@praxisui/stepper 7.0.0-beta.0 → 8.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import * as i6 from '@angular/material/button';
9
9
  import { MatButtonModule } from '@angular/material/button';
10
10
  import * as i5$1 from '@angular/material/icon';
11
11
  import { MatIconModule } from '@angular/material/icon';
12
- import { providePraxisI18n, PraxisI18nService, IconPickerService, PraxisIconDirective, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, LoggerService, DynamicWidgetLoaderDirective, EmptyStateCardComponent, EDITORIAL_WIDGET_TAG, ComponentMetadataRegistry, createCorporateLoggerConfig, ConsoleLoggerSink, CONFIG_STORAGE } from '@praxisui/core';
12
+ import { providePraxisI18n, PraxisI18nService, IconPickerService, createEmptyRichContentDocument, PraxisIconDirective, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, LoggerService, DynamicWidgetLoaderDirective, EmptyStateCardComponent, EDITORIAL_WIDGET_TAG, ComponentMetadataRegistry, createCorporateLoggerConfig, ConsoleLoggerSink, CONFIG_STORAGE } from '@praxisui/core';
13
13
  import * as i2 from '@angular/forms';
14
14
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
15
15
  import { PraxisDynamicFormConfigEditor, PraxisDynamicForm } from '@praxisui/dynamic-form';
@@ -26,9 +26,7 @@ import * as i9 from '@angular/material/tooltip';
26
26
  import { MatTooltipModule } from '@angular/material/tooltip';
27
27
  import * as i1 from '@angular/material/dialog';
28
28
  import { MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
29
- import * as i10 from '@angular/material/menu';
30
- import { MatMenuModule } from '@angular/material/menu';
31
- import * as i11 from '@angular/material/tabs';
29
+ import * as i10 from '@angular/material/tabs';
32
30
  import { MatTabsModule } from '@angular/material/tabs';
33
31
  import { BehaviorSubject } from 'rxjs';
34
32
  import { PraxisListConfigEditor } from '@praxisui/list';
@@ -37,6 +35,7 @@ import { ComponentPaletteDialogComponent } from '@praxisui/page-builder';
37
35
  import * as i5 from '@angular/material/checkbox';
38
36
  import { MatCheckboxModule } from '@angular/material/checkbox';
39
37
  import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
38
+ import { PraxisRichContent } from '@praxisui/rich-content';
40
39
  import { take } from 'rxjs/operators';
41
40
  import * as i1$2 from '@angular/material/card';
42
41
  import { MatCardModule } from '@angular/material/card';
@@ -138,376 +137,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
138
137
  args: [MAT_DIALOG_DATA]
139
138
  }] }] });
140
139
 
141
- class WizardWidgetConfigDialogComponent {
142
- ref;
143
- wizardWidgetId;
144
- dialogTitle;
145
- model;
146
- originalInputs;
147
- constructor(ref, data) {
148
- this.ref = ref;
149
- const id = (data?.widgetId || '');
150
- this.wizardWidgetId = this.isWizardWidgetId(id) ? id : null;
151
- this.originalInputs = { ...(data?.inputs || {}) };
152
- this.model = this.buildDraft(this.originalInputs);
153
- this.dialogTitle = this.resolveTitle(this.wizardWidgetId);
154
- }
155
- get benefits() {
156
- if (!Array.isArray(this.model.items))
157
- this.model.items = [];
158
- return this.model.items;
159
- }
160
- addBenefit() {
161
- this.benefits.push({ title: '', text: '', icon: '' });
162
- }
163
- removeBenefit(index) {
164
- this.benefits.splice(index, 1);
165
- }
166
- get statusMessage() {
167
- switch (this.wizardWidgetId) {
168
- case 'praxis-wizard-content':
169
- return {
170
- title: 'Legado em deprecação',
171
- body: 'Este bloco continua suportado para compatibilidade, mas novo authoring deve preferir widget:rich-text-block. O shape atual do wizard segue válido neste ciclo.',
172
- };
173
- case 'praxis-wizard-inline-notice':
174
- return {
175
- title: 'Semântica em aberto',
176
- body: 'Este bloco permanece ativo no wizard até a definição final entre legal-notice e um eventual info-callout compartilhado.',
177
- };
178
- case 'praxis-wizard-benefits':
179
- return {
180
- title: 'Mantido temporariamente',
181
- body: 'Ainda não existe equivalente canônico compartilhado para este grid no backlog editorial atual.',
182
- };
183
- case 'praxis-wizard-divider':
184
- return {
185
- title: 'Mantido temporariamente',
186
- body: 'Este divisor continua como bloco legado do wizard enquanto não houver equivalente compartilhado priorizado.',
187
- };
188
- default:
189
- return null;
190
- }
191
- }
192
- cancel() {
193
- this.ref.close();
194
- }
195
- save() {
196
- const next = { ...this.originalInputs, ...this.buildInputsForWidget() };
197
- if (typeof next.blockId === 'string') {
198
- next.blockId = next.blockId.trim();
199
- }
200
- if (!next.blockId)
201
- delete next.blockId;
202
- this.ref.close({ inputs: next });
203
- }
204
- isWizardWidgetId(value) {
205
- return (value === 'praxis-wizard-benefits'
206
- || value === 'praxis-wizard-content'
207
- || value === 'praxis-wizard-inline-notice'
208
- || value === 'praxis-wizard-divider');
209
- }
210
- resolveTitle(id) {
211
- switch (id) {
212
- case 'praxis-wizard-benefits':
213
- return 'Editar grade de benefícios';
214
- case 'praxis-wizard-content':
215
- return 'Editar bloco de conteúdo';
216
- case 'praxis-wizard-inline-notice':
217
- return 'Editar aviso inline';
218
- case 'praxis-wizard-divider':
219
- return 'Editar divisor';
220
- default:
221
- return 'Editar Widget do Wizard';
222
- }
223
- }
224
- buildDraft(inputs) {
225
- return {
226
- blockId: inputs.blockId || '',
227
- title: inputs.title || '',
228
- subtitle: inputs.subtitle || '',
229
- text: inputs.text || '',
230
- caption: inputs.caption || '',
231
- tone: inputs.tone || 'neutral',
232
- label: inputs.label || '',
233
- columns: Number.isFinite(Number(inputs.columns)) ? Number(inputs.columns) : 4,
234
- boxed: !!inputs.boxed,
235
- items: Array.isArray(inputs.items)
236
- ? inputs.items.map((item) => ({ icon: item?.icon || '', title: item?.title || '', text: item?.text || '' }))
237
- : [],
238
- };
239
- }
240
- buildInputsForWidget() {
241
- if (!this.wizardWidgetId)
242
- return { ...this.model };
243
- const base = {};
244
- if (this.model.blockId?.trim())
245
- base.blockId = this.model.blockId.trim();
246
- switch (this.wizardWidgetId) {
247
- case 'praxis-wizard-inline-notice':
248
- base.text = this.model.text || '';
249
- base.tone = this.model.tone || 'neutral';
250
- return base;
251
- case 'praxis-wizard-content':
252
- base.title = this.model.title || '';
253
- base.subtitle = this.model.subtitle || '';
254
- base.text = this.model.text || '';
255
- base.caption = this.model.caption || '';
256
- return base;
257
- case 'praxis-wizard-divider':
258
- base.label = this.model.label || '';
259
- return base;
260
- case 'praxis-wizard-benefits':
261
- base.title = this.model.title || '';
262
- base.columns = this.safeColumns(this.model.columns);
263
- base.boxed = !!this.model.boxed;
264
- base.items = this.benefits
265
- .map((item) => ({
266
- icon: item.icon || '',
267
- title: item.title || '',
268
- text: item.text || '',
269
- }))
270
- .filter((item) => item.title || item.text || item.icon);
271
- return base;
272
- default:
273
- return base;
274
- }
275
- }
276
- safeColumns(value) {
277
- const parsed = Number(value);
278
- if (!Number.isFinite(parsed) || parsed < 1)
279
- return 1;
280
- return Math.floor(parsed);
281
- }
282
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: WizardWidgetConfigDialogComponent, deps: [{ token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component });
283
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: WizardWidgetConfigDialogComponent, isStandalone: true, selector: "praxis-wizard-widget-config-dialog", ngImport: i0, template: `
284
- <h2 mat-dialog-title>{{ dialogTitle }}</h2>
285
-
286
- <div mat-dialog-content class="dialog-grid">
287
- <div class="status-banner" *ngIf="statusMessage as status">
288
- <strong>{{ status.title }}</strong>
289
- <span>{{ status.body }}</span>
290
- </div>
291
-
292
- <ng-container [ngSwitch]="wizardWidgetId">
293
- <ng-container *ngSwitchCase="'praxis-wizard-inline-notice'">
294
- <mat-form-field appearance="outline" class="full">
295
- <mat-label>Texto</mat-label>
296
- <input matInput [(ngModel)]="model.text" placeholder="Mensagem do aviso" />
297
- </mat-form-field>
298
- <mat-form-field appearance="outline">
299
- <mat-label>Tom</mat-label>
300
- <select matNativeControl [(ngModel)]="model.tone">
301
- <option value="neutral">Neutro</option>
302
- <option value="info">Informativo</option>
303
- <option value="warning">Atenção</option>
304
- </select>
305
- </mat-form-field>
306
- </ng-container>
307
-
308
- <ng-container *ngSwitchCase="'praxis-wizard-content'">
309
- <mat-form-field appearance="outline">
310
- <mat-label>Título</mat-label>
311
- <input matInput [(ngModel)]="model.title" />
312
- </mat-form-field>
313
- <mat-form-field appearance="outline">
314
- <mat-label>Subtítulo</mat-label>
315
- <input matInput [(ngModel)]="model.subtitle" />
316
- </mat-form-field>
317
- <mat-form-field appearance="outline" class="full">
318
- <mat-label>Texto</mat-label>
319
- <input matInput [(ngModel)]="model.text" />
320
- </mat-form-field>
321
- <mat-form-field appearance="outline" class="full">
322
- <mat-label>Legenda</mat-label>
323
- <input matInput [(ngModel)]="model.caption" />
324
- </mat-form-field>
325
- </ng-container>
326
-
327
- <ng-container *ngSwitchCase="'praxis-wizard-divider'">
328
- <mat-form-field appearance="outline" class="full">
329
- <mat-label>Rótulo</mat-label>
330
- <input matInput [(ngModel)]="model.label" placeholder="Opcional" />
331
- </mat-form-field>
332
- </ng-container>
333
-
334
- <ng-container *ngSwitchCase="'praxis-wizard-benefits'">
335
- <mat-form-field appearance="outline">
336
- <mat-label>Título</mat-label>
337
- <input matInput [(ngModel)]="model.title" />
338
- </mat-form-field>
339
- <mat-form-field appearance="outline">
340
- <mat-label>Colunas</mat-label>
341
- <input matInput type="number" min="1" [(ngModel)]="model.columns" />
342
- </mat-form-field>
343
- <mat-checkbox [(ngModel)]="model.boxed">Cards com contorno</mat-checkbox>
344
-
345
- <div class="benefits">
346
- <div class="benefits-head">
347
- <strong>Itens de benefício</strong>
348
- <button mat-stroked-button type="button" (click)="addBenefit()">Adicionar item</button>
349
- </div>
350
- <div class="benefit-card" *ngFor="let item of benefits; let i = index">
351
- <div class="benefit-head">
352
- <span>Item {{ i + 1 }}</span>
353
- <button mat-button color="warn" type="button" (click)="removeBenefit(i)">Remover</button>
354
- </div>
355
- <mat-form-field appearance="outline">
356
- <mat-label>Ícone</mat-label>
357
- <input matInput [(ngModel)]="item.icon" placeholder="Ex.: info" />
358
- </mat-form-field>
359
- <mat-form-field appearance="outline">
360
- <mat-label>Título</mat-label>
361
- <input matInput [(ngModel)]="item.title" />
362
- </mat-form-field>
363
- <mat-form-field appearance="outline" class="full">
364
- <mat-label>Texto</mat-label>
365
- <input matInput [(ngModel)]="item.text" />
366
- </mat-form-field>
367
- </div>
368
- </div>
369
- </ng-container>
370
- </ng-container>
371
-
372
- <details class="advanced full">
373
- <summary>Avançado</summary>
374
- <div class="advanced-body">
375
- <mat-form-field appearance="outline" class="full">
376
- <mat-label>Block ID legado (avançado)</mat-label>
377
- <input matInput [(ngModel)]="model.blockId" placeholder="Ex.: ft-edit-wizard:access:block:hero" />
378
- <mat-hint>Identificador operacional legado do wizard para telemetria/E2E e compatibilidade com editor/adapter. No modelo convergido, instanceId será a convenção editorial preferencial.</mat-hint>
379
- </mat-form-field>
380
- </div>
381
- </details>
382
- </div>
383
-
384
- <div mat-dialog-actions align="end">
385
- <button mat-button type="button" (click)="cancel()">Cancelar</button>
386
- <button mat-flat-button color="primary" type="button" (click)="save()">Salvar</button>
387
- </div>
388
- `, isInline: true, styles: [".dialog-grid{display:grid;gap:12px;min-width:min(760px,92vw)}.full{grid-column:1 / -1}.status-banner{display:grid;gap:4px;padding:10px 12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:12px}.benefits{display:grid;gap:10px;border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.benefits-head{display:flex;justify-content:space-between;align-items:center;gap:8px}.benefit-card{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));padding:10px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest)}.benefit-head{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.advanced{border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);padding:8px 10px;background:var(--md-sys-color-surface-container-lowest)}.advanced>summary{cursor:pointer;font-weight:600;list-style:none}.advanced>summary::-webkit-details-marker{display:none}.advanced-body{margin-top:10px;display:grid;gap:8px}\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: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "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: 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"] }] });
389
- }
390
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: WizardWidgetConfigDialogComponent, decorators: [{
391
- type: Component,
392
- args: [{ selector: 'praxis-wizard-widget-config-dialog', standalone: true, imports: [
393
- CommonModule,
394
- FormsModule,
395
- MatDialogModule,
396
- MatFormFieldModule,
397
- MatInputModule,
398
- MatCheckboxModule,
399
- MatButtonModule,
400
- ], template: `
401
- <h2 mat-dialog-title>{{ dialogTitle }}</h2>
402
-
403
- <div mat-dialog-content class="dialog-grid">
404
- <div class="status-banner" *ngIf="statusMessage as status">
405
- <strong>{{ status.title }}</strong>
406
- <span>{{ status.body }}</span>
407
- </div>
408
-
409
- <ng-container [ngSwitch]="wizardWidgetId">
410
- <ng-container *ngSwitchCase="'praxis-wizard-inline-notice'">
411
- <mat-form-field appearance="outline" class="full">
412
- <mat-label>Texto</mat-label>
413
- <input matInput [(ngModel)]="model.text" placeholder="Mensagem do aviso" />
414
- </mat-form-field>
415
- <mat-form-field appearance="outline">
416
- <mat-label>Tom</mat-label>
417
- <select matNativeControl [(ngModel)]="model.tone">
418
- <option value="neutral">Neutro</option>
419
- <option value="info">Informativo</option>
420
- <option value="warning">Atenção</option>
421
- </select>
422
- </mat-form-field>
423
- </ng-container>
424
-
425
- <ng-container *ngSwitchCase="'praxis-wizard-content'">
426
- <mat-form-field appearance="outline">
427
- <mat-label>Título</mat-label>
428
- <input matInput [(ngModel)]="model.title" />
429
- </mat-form-field>
430
- <mat-form-field appearance="outline">
431
- <mat-label>Subtítulo</mat-label>
432
- <input matInput [(ngModel)]="model.subtitle" />
433
- </mat-form-field>
434
- <mat-form-field appearance="outline" class="full">
435
- <mat-label>Texto</mat-label>
436
- <input matInput [(ngModel)]="model.text" />
437
- </mat-form-field>
438
- <mat-form-field appearance="outline" class="full">
439
- <mat-label>Legenda</mat-label>
440
- <input matInput [(ngModel)]="model.caption" />
441
- </mat-form-field>
442
- </ng-container>
443
-
444
- <ng-container *ngSwitchCase="'praxis-wizard-divider'">
445
- <mat-form-field appearance="outline" class="full">
446
- <mat-label>Rótulo</mat-label>
447
- <input matInput [(ngModel)]="model.label" placeholder="Opcional" />
448
- </mat-form-field>
449
- </ng-container>
450
-
451
- <ng-container *ngSwitchCase="'praxis-wizard-benefits'">
452
- <mat-form-field appearance="outline">
453
- <mat-label>Título</mat-label>
454
- <input matInput [(ngModel)]="model.title" />
455
- </mat-form-field>
456
- <mat-form-field appearance="outline">
457
- <mat-label>Colunas</mat-label>
458
- <input matInput type="number" min="1" [(ngModel)]="model.columns" />
459
- </mat-form-field>
460
- <mat-checkbox [(ngModel)]="model.boxed">Cards com contorno</mat-checkbox>
461
-
462
- <div class="benefits">
463
- <div class="benefits-head">
464
- <strong>Itens de benefício</strong>
465
- <button mat-stroked-button type="button" (click)="addBenefit()">Adicionar item</button>
466
- </div>
467
- <div class="benefit-card" *ngFor="let item of benefits; let i = index">
468
- <div class="benefit-head">
469
- <span>Item {{ i + 1 }}</span>
470
- <button mat-button color="warn" type="button" (click)="removeBenefit(i)">Remover</button>
471
- </div>
472
- <mat-form-field appearance="outline">
473
- <mat-label>Ícone</mat-label>
474
- <input matInput [(ngModel)]="item.icon" placeholder="Ex.: info" />
475
- </mat-form-field>
476
- <mat-form-field appearance="outline">
477
- <mat-label>Título</mat-label>
478
- <input matInput [(ngModel)]="item.title" />
479
- </mat-form-field>
480
- <mat-form-field appearance="outline" class="full">
481
- <mat-label>Texto</mat-label>
482
- <input matInput [(ngModel)]="item.text" />
483
- </mat-form-field>
484
- </div>
485
- </div>
486
- </ng-container>
487
- </ng-container>
488
-
489
- <details class="advanced full">
490
- <summary>Avançado</summary>
491
- <div class="advanced-body">
492
- <mat-form-field appearance="outline" class="full">
493
- <mat-label>Block ID legado (avançado)</mat-label>
494
- <input matInput [(ngModel)]="model.blockId" placeholder="Ex.: ft-edit-wizard:access:block:hero" />
495
- <mat-hint>Identificador operacional legado do wizard para telemetria/E2E e compatibilidade com editor/adapter. No modelo convergido, instanceId será a convenção editorial preferencial.</mat-hint>
496
- </mat-form-field>
497
- </div>
498
- </details>
499
- </div>
500
-
501
- <div mat-dialog-actions align="end">
502
- <button mat-button type="button" (click)="cancel()">Cancelar</button>
503
- <button mat-flat-button color="primary" type="button" (click)="save()">Salvar</button>
504
- </div>
505
- `, styles: [".dialog-grid{display:grid;gap:12px;min-width:min(760px,92vw)}.full{grid-column:1 / -1}.status-banner{display:grid;gap:4px;padding:10px 12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:12px}.benefits{display:grid;gap:10px;border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.benefits-head{display:flex;justify-content:space-between;align-items:center;gap:8px}.benefit-card{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));padding:10px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest)}.benefit-head{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.advanced{border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);padding:8px 10px;background:var(--md-sys-color-surface-container-lowest)}.advanced>summary{cursor:pointer;font-weight:600;list-style:none}.advanced>summary::-webkit-details-marker{display:none}.advanced-body{margin-top:10px;display:grid;gap:8px}\n"] }]
506
- }], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: undefined, decorators: [{
507
- type: Inject,
508
- args: [MAT_DIALOG_DATA]
509
- }] }] });
510
-
511
140
  const PRAXIS_STEPPER_EN_US = {
512
141
  'praxis.stepper.editor.tabs.general': 'General',
513
142
  'praxis.stepper.editor.tabs.steps': 'Steps',
@@ -709,7 +338,7 @@ const PRAXIS_STEPPER_EN_US = {
709
338
  'praxis.stepper.editor.summary.noContent': 'No content',
710
339
  'praxis.stepper.editor.summary.labelPrefix': 'Label: {{label}}',
711
340
  'praxis.stepper.editor.summary.dividerNoLabel': 'Divider without label',
712
- 'praxis.stepper.editor.status.contentLegacy': 'Deprecated legacy: new authoring should prefer widget:rich-text-block. This block remains supported for compatibility in this cycle.',
341
+ 'praxis.stepper.editor.status.contentLegacy': 'Deprecated legacy: new authoring should prefer step rich content documents. This block remains supported only for existing configurations.',
713
342
  'praxis.stepper.editor.status.noticeCurrent': 'Kept in the current cycle: final semantics are still being defined between legal-notice and info-callout.',
714
343
  'praxis.stepper.editor.status.benefitsTemporary': 'Temporarily kept: still no canonical equivalent in the editorial backlog.',
715
344
  'praxis.stepper.editor.status.dividerTemporary': 'Temporarily kept: legacy separator without a prioritized shared equivalent.',
@@ -920,7 +549,7 @@ const PRAXIS_STEPPER_PT_BR = {
920
549
  'praxis.stepper.editor.summary.noContent': 'Sem conteúdo',
921
550
  'praxis.stepper.editor.summary.labelPrefix': 'Rótulo: {{label}}',
922
551
  'praxis.stepper.editor.summary.dividerNoLabel': 'Divisor sem rótulo',
923
- 'praxis.stepper.editor.status.contentLegacy': 'Legado em depreciação: novas criações devem preferir widget:rich-text-block. O bloco continua suportado por compatibilidade neste ciclo.',
552
+ 'praxis.stepper.editor.status.contentLegacy': 'Legado em depreciação: novas criações devem preferir documentos de rich content da etapa. O bloco continua suportado apenas para configurações existentes.',
924
553
  'praxis.stepper.editor.status.noticeCurrent': 'Mantido no ciclo atual: semântica final ainda em definição entre aviso-legal (legal-notice) e destaque-informativo (info-callout).',
925
554
  'praxis.stepper.editor.status.benefitsTemporary': 'Mantido temporariamente: ainda sem equivalente canônico no backlog editorial.',
926
555
  'praxis.stepper.editor.status.dividerTemporary': 'Mantido temporariamente: separador legado sem equivalente compartilhado priorizado.',
@@ -1268,150 +897,78 @@ class PraxisStepperConfigEditor {
1268
897
  }
1269
898
  ensureWidgets() { const s = this.activeStep; if (!s.widgets)
1270
899
  s.widgets = []; return s.widgets; }
1271
- ensureWidgetsBeforeForm() { const s = this.activeStep; if (!s.widgetsBeforeForm)
1272
- s.widgetsBeforeForm = []; return s.widgetsBeforeForm; }
1273
900
  displayWidgetName(w) {
1274
901
  switch (w.id) {
1275
902
  case 'praxis-list': return this.tx('editor.widgets.listSelection', 'List (selection)');
1276
903
  case 'pdx-material-searchable-select': return this.tx('editor.widgets.searchableSelection', 'Searchable selection');
1277
904
  case 'praxis-files-upload': return this.tx('editor.widgets.fileUpload', 'File upload');
1278
905
  case 'praxis-dynamic-form': return this.tx('editor.widgets.form', 'Form');
1279
- case 'praxis-wizard-benefits': return this.tx('editor.widgets.benefits', 'Benefits grid');
1280
- case 'praxis-wizard-content': return this.tx('editor.widgets.content', 'Content block');
1281
- case 'praxis-wizard-inline-notice': return this.tx('editor.widgets.notice', 'Inline notice');
1282
- case 'praxis-wizard-divider': return this.tx('editor.widgets.divider', 'Divider');
1283
906
  default: return w.inputs?.title || w.inputs?.label || this.tx('editor.widgets.default', 'Widget');
1284
907
  }
1285
908
  }
1286
- editorialBeforeWidgets(step) {
1287
- return (step.widgetsBeforeForm || []).filter((w) => this.isWizardWidgetId(w.id));
1288
- }
1289
- editorialAfterWidgets(step) {
1290
- return (step.widgets || []).filter((w) => this.isWizardWidgetId(w.id));
1291
- }
1292
909
  advancedWidgets(step) {
1293
- return (step.widgets || []).filter((w) => !this.isWizardWidgetId(w.id));
1294
- }
1295
- widgetBlockId(w) {
1296
- const blockId = String(w.inputs?.blockId || '').trim();
1297
- return blockId || null;
1298
- }
1299
- editorialWidgetSummary(w) {
1300
- const inputs = w.inputs || {};
1301
- const shorten = (value, max = 72) => {
1302
- const text = (value || '').trim();
1303
- if (!text)
1304
- return '';
1305
- return text.length > max ? `${text.slice(0, max - 3)}...` : text;
1306
- };
1307
- switch (w.id) {
1308
- case 'praxis-wizard-benefits': {
1309
- const count = Array.isArray(inputs.items) ? inputs.items.length : 0;
1310
- const cols = Number.isFinite(Number(inputs.columns)) ? Number(inputs.columns) : 4;
1311
- return this.tx('editor.summary.itemsColumns', '{{count}} item(s) • {{cols}} column(s)')
1312
- .replace('{{count}}', `${count}`)
1313
- .replace('{{cols}}', `${cols}`);
1314
- }
1315
- case 'praxis-wizard-content': {
1316
- const text = shorten(inputs.title || inputs.subtitle || inputs.text || inputs.caption, 90);
1317
- return text || this.tx('editor.summary.noContent', 'No content');
1318
- }
1319
- case 'praxis-wizard-inline-notice': {
1320
- const tone = this.toneLabel(inputs.tone);
1321
- const text = shorten(inputs.text, 90);
1322
- return text ? `${tone} • ${text}` : tone;
1323
- }
1324
- case 'praxis-wizard-divider':
1325
- return inputs.label
1326
- ? this.tx('editor.summary.labelPrefix', 'Label: {{label}}').replace('{{label}}', inputs.label)
1327
- : this.tx('editor.summary.dividerNoLabel', 'Divider without label');
1328
- default:
1329
- return '';
1330
- }
910
+ return step.widgets || [];
1331
911
  }
1332
- editorialWidgetStatus(w) {
1333
- switch (w.id) {
1334
- case 'praxis-wizard-content':
1335
- return this.tx('editor.status.contentLegacy', 'Deprecated legacy: new authoring should prefer widget:rich-text-block. This block remains supported for compatibility in this cycle.');
1336
- case 'praxis-wizard-inline-notice':
1337
- return this.tx('editor.status.noticeCurrent', 'Kept in the current cycle: final semantics are still being defined between legal-notice and info-callout.');
1338
- case 'praxis-wizard-benefits':
1339
- return this.tx('editor.status.benefitsTemporary', 'Temporarily kept: still no canonical equivalent in the editorial backlog.');
1340
- case 'praxis-wizard-divider':
1341
- return this.tx('editor.status.dividerTemporary', 'Temporarily kept: legacy separator without a prioritized shared equivalent.');
1342
- default:
1343
- return this.tx('editor.status.editorialWidget', 'Wizard editorial widget.');
1344
- }
912
+ richContentNodeCount(document) {
913
+ return Array.isArray(document?.nodes) ? document.nodes.length : 0;
1345
914
  }
1346
- addEditorialWidget(id, zone = 'before') {
1347
- const step = this.activeStep;
1348
- if (!step)
1349
- return;
1350
- const defaults = this.defaultEditorialInputs(id, step, zone);
1351
- this.openWizardWidgetDialog(id, defaults).subscribe((result) => {
1352
- if (!result?.inputs)
1353
- return;
1354
- const wd = {
1355
- id,
1356
- inputs: { ...defaults, ...result.inputs },
1357
- };
915
+ richContentJson(step, zone) {
916
+ const document = zone === 'before' ? step.stepBlocksBeforeForm : step.stepBlocksAfterForm;
917
+ return JSON.stringify(document ?? createEmptyRichContentDocument(), null, 2);
918
+ }
919
+ setRichContentJson(step, zone, value) {
920
+ try {
921
+ const parsed = JSON.parse(value || '{}');
922
+ const next = this.normalizeRichContentDocument(parsed);
1358
923
  if (zone === 'before')
1359
- this.ensureWidgetsBeforeForm().push(wd);
924
+ step.stepBlocksBeforeForm = next;
1360
925
  else
1361
- this.ensureWidgets().push(wd);
926
+ step.stepBlocksAfterForm = next;
927
+ this.isValid$.next(true);
1362
928
  this.markDirty();
1363
- });
1364
- }
1365
- dropEditorialBefore(ev) {
1366
- const step = this.activeStep;
1367
- if (!step)
1368
- return;
1369
- const list = this.ensureWidgetsBeforeForm();
1370
- this.reorderSubset(list, (w) => this.isWizardWidgetId(w.id), ev.previousIndex, ev.currentIndex);
1371
- this.markDirty();
1372
- }
1373
- dropEditorialAfter(ev) {
1374
- const step = this.activeStep;
1375
- if (!step)
1376
- return;
1377
- const list = this.ensureWidgets();
1378
- this.reorderSubset(list, (w) => this.isWizardWidgetId(w.id), ev.previousIndex, ev.currentIndex);
1379
- this.markDirty();
929
+ }
930
+ catch {
931
+ this.isValid$.next(false);
932
+ }
1380
933
  }
1381
- moveEditorialWidget(w, from, to) {
1382
- if (from === to)
1383
- return;
1384
- const fromList = from === 'before' ? this.ensureWidgetsBeforeForm() : this.ensureWidgets();
1385
- const toList = to === 'before' ? this.ensureWidgetsBeforeForm() : this.ensureWidgets();
1386
- const idx = fromList.indexOf(w);
1387
- if (idx < 0)
1388
- return;
1389
- fromList.splice(idx, 1);
1390
- toList.push(w);
934
+ seedRichContent(step, zone) {
935
+ const label = step.label || this.tx('editor.defaults.newStep', 'New step');
936
+ const document = {
937
+ kind: 'praxis.rich-content',
938
+ version: '1.0.0',
939
+ nodes: [
940
+ {
941
+ type: 'card',
942
+ title: zone === 'before'
943
+ ? this.tx('editor.steps.seedBeforeTitle', 'Step context')
944
+ : this.tx('editor.steps.seedAfterTitle', 'Follow-up'),
945
+ content: [
946
+ { type: 'text', text: label },
947
+ { type: 'badge', label: zone === 'before' ? 'before' : 'after' },
948
+ ],
949
+ },
950
+ ],
951
+ };
952
+ if (zone === 'before')
953
+ step.stepBlocksBeforeForm = document;
954
+ else
955
+ step.stepBlocksAfterForm = document;
1391
956
  this.markDirty();
1392
957
  }
1393
- duplicateEditorialWidget(w, zone) {
1394
- const step = this.activeStep;
1395
- if (!step || !this.isWizardWidgetId(w.id))
1396
- return;
1397
- const clone = JSON.parse(JSON.stringify(w));
1398
- const inputs = (clone.inputs = { ...(clone.inputs || {}) });
1399
- inputs.blockId = this.nextEditorialBlockId(step, w.id, zone);
1400
- const list = zone === 'before' ? this.ensureWidgetsBeforeForm() : this.ensureWidgets();
1401
- const idx = list.indexOf(w);
1402
- if (idx >= 0)
1403
- list.splice(idx + 1, 0, clone);
958
+ clearRichContent(step, zone) {
959
+ if (zone === 'before')
960
+ step.stepBlocksBeforeForm = null;
1404
961
  else
1405
- list.push(clone);
962
+ step.stepBlocksAfterForm = null;
1406
963
  this.markDirty();
1407
964
  }
1408
- removeEditorialWidget(w, zone) {
1409
- const list = zone === 'before' ? this.ensureWidgetsBeforeForm() : this.ensureWidgets();
1410
- const idx = list.indexOf(w);
1411
- if (idx < 0)
1412
- return;
1413
- list.splice(idx, 1);
1414
- this.markDirty();
965
+ normalizeRichContentDocument(document) {
966
+ return {
967
+ kind: 'praxis.rich-content',
968
+ version: '1.0.0',
969
+ context: document.context,
970
+ nodes: Array.isArray(document.nodes) ? document.nodes : [],
971
+ };
1415
972
  }
1416
973
  removeAdvancedWidget(w) {
1417
974
  const widgets = this.ensureWidgets();
@@ -1435,98 +992,7 @@ class PraxisStepperConfigEditor {
1435
992
  return (w.id === 'praxis-dynamic-form'
1436
993
  || w.id === 'praxis-list'
1437
994
  || w.id === 'pdx-material-searchable-select'
1438
- || w.id === 'praxis-files-upload'
1439
- || this.isWizardWidgetId(w.id));
1440
- }
1441
- defaultEditorialInputs(id, step, zone) {
1442
- const blockId = this.nextEditorialBlockId(step, id, zone);
1443
- switch (id) {
1444
- case 'praxis-wizard-benefits':
1445
- return {
1446
- blockId,
1447
- title: '',
1448
- columns: 4,
1449
- boxed: true,
1450
- items: [{ icon: '', title: this.tx('editor.defaults.newBenefit', 'New benefit'), text: '' }],
1451
- };
1452
- case 'praxis-wizard-content':
1453
- return {
1454
- blockId,
1455
- title: '',
1456
- subtitle: '',
1457
- text: '',
1458
- caption: '',
1459
- };
1460
- case 'praxis-wizard-inline-notice':
1461
- return {
1462
- blockId,
1463
- text: '',
1464
- tone: 'neutral',
1465
- };
1466
- case 'praxis-wizard-divider':
1467
- return {
1468
- blockId,
1469
- label: '',
1470
- };
1471
- default:
1472
- return { blockId };
1473
- }
1474
- }
1475
- nextEditorialBlockId(step, id, zone) {
1476
- const stepPart = this.normalizeBlockIdPart(step.id || `step-${this.activeIndex + 1}`);
1477
- const kind = id.replace('praxis-wizard-', '');
1478
- const prefix = `${stepPart}:${zone}:${kind}:`;
1479
- const used = new Set();
1480
- let max = 0;
1481
- const allWidgets = [...(step.widgetsBeforeForm || []), ...(step.widgets || [])];
1482
- allWidgets.forEach((widget) => {
1483
- const blockId = String(widget.inputs?.blockId || '').trim();
1484
- if (!blockId.startsWith(prefix))
1485
- return;
1486
- used.add(blockId);
1487
- const suffix = Number(blockId.slice(prefix.length));
1488
- if (Number.isFinite(suffix)) {
1489
- max = Math.max(max, suffix);
1490
- }
1491
- });
1492
- let nextIdx = Math.max(1, max + 1);
1493
- let candidate = `${prefix}${nextIdx}`;
1494
- while (used.has(candidate)) {
1495
- nextIdx += 1;
1496
- candidate = `${prefix}${nextIdx}`;
1497
- }
1498
- return candidate;
1499
- }
1500
- normalizeBlockIdPart(value) {
1501
- const normalized = (value || '')
1502
- .trim()
1503
- .toLowerCase()
1504
- .replace(/\s+/g, '-')
1505
- .replace(/[^a-z0-9:_-]/g, '');
1506
- return normalized || `step-${this.activeIndex + 1}`;
1507
- }
1508
- toneLabel(value) {
1509
- switch (value) {
1510
- case 'info': return this.tx('editor.tone.info', 'Informational');
1511
- case 'warning': return this.tx('editor.tone.warning', 'Warning');
1512
- default: return this.tx('editor.tone.neutral', 'Neutral');
1513
- }
1514
- }
1515
- reorderSubset(list, pick, fromSubsetIndex, toSubsetIndex) {
1516
- const subsetIndices = [];
1517
- list.forEach((item, index) => {
1518
- if (pick(item))
1519
- subsetIndices.push(index);
1520
- });
1521
- const fromReal = subsetIndices[fromSubsetIndex];
1522
- const toReal = subsetIndices[toSubsetIndex];
1523
- if (fromReal == null || toReal == null)
1524
- return;
1525
- const [moved] = list.splice(fromReal, 1);
1526
- let insertAt = toReal;
1527
- if (fromReal < toReal)
1528
- insertAt -= 1;
1529
- list.splice(insertAt, 0, moved);
995
+ || w.id === 'praxis-files-upload');
1530
996
  }
1531
997
  // ------- Main form -------
1532
998
  addMainForm() {
@@ -1700,44 +1166,11 @@ class PraxisStepperConfigEditor {
1700
1166
  ref.saved$.subscribe(apply);
1701
1167
  break;
1702
1168
  }
1703
- case 'praxis-wizard-benefits':
1704
- case 'praxis-wizard-content':
1705
- case 'praxis-wizard-inline-notice':
1706
- case 'praxis-wizard-divider': {
1707
- this.openWizardWidgetEditor(w);
1708
- break;
1709
- }
1710
1169
  default:
1711
1170
  // No editor available
1712
1171
  break;
1713
1172
  }
1714
1173
  }
1715
- openWizardWidgetEditor(w) {
1716
- if (!this.isWizardWidgetId(w.id))
1717
- return;
1718
- this.openWizardWidgetDialog(w.id, { ...(w.inputs || {}) }).subscribe((result) => {
1719
- if (!result?.inputs)
1720
- return;
1721
- w.inputs = { ...(w.inputs || {}), ...result.inputs };
1722
- this.markDirty();
1723
- });
1724
- }
1725
- openWizardWidgetDialog(widgetId, inputs) {
1726
- const dialogRef = this.dialog.open(WizardWidgetConfigDialogComponent, {
1727
- width: '760px',
1728
- data: {
1729
- widgetId,
1730
- inputs: { ...inputs },
1731
- },
1732
- });
1733
- return dialogRef.afterClosed();
1734
- }
1735
- isWizardWidgetId(id) {
1736
- return (id === 'praxis-wizard-benefits'
1737
- || id === 'praxis-wizard-content'
1738
- || id === 'praxis-wizard-inline-notice'
1739
- || id === 'praxis-wizard-divider');
1740
- }
1741
1174
  // SettingsValueProvider
1742
1175
  getSettingsValue() { return this.config; }
1743
1176
  onSave() { return this.getSettingsValue(); }
@@ -1970,114 +1403,52 @@ class PraxisStepperConfigEditor {
1970
1403
  <div class="zone-head">
1971
1404
  <div class="zone-title">{{ tx('editor.steps.zoneBefore', 'Before form') }}</div>
1972
1405
  <div class="zone-actions">
1973
- <span class="zone-count">{{ editorialBeforeWidgets(step).length }}</span>
1974
- <button mat-stroked-button [matMenuTriggerFor]="beforeEditorialMenu">
1975
- <mat-icon [praxisIcon]="'add'"></mat-icon>
1976
- {{ tx('editor.steps.addBlock', 'Add') }}
1406
+ <span class="zone-count">{{ richContentNodeCount(step.stepBlocksBeforeForm) }}</span>
1407
+ <button mat-stroked-button type="button" (click)="seedRichContent(step, 'before')">
1408
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
1409
+ {{ tx('editor.steps.seedRichContent', 'Seed') }}
1977
1410
  </button>
1978
- <mat-menu #beforeEditorialMenu="matMenu">
1979
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-benefits', 'before')">
1980
- <mat-icon [praxisIcon]="'view_module'"></mat-icon>
1981
- {{ tx('editor.steps.widgetBenefits', 'Benefits grid') }}
1982
- </button>
1983
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-content', 'before')">
1984
- <mat-icon [praxisIcon]="'article'"></mat-icon>
1985
- {{ tx('editor.steps.widgetContent', 'Content block') }}
1986
- </button>
1987
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-inline-notice', 'before')">
1988
- <mat-icon [praxisIcon]="'info'"></mat-icon>
1989
- {{ tx('editor.steps.widgetNotice', 'Inline notice') }}
1990
- </button>
1991
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-divider', 'before')">
1992
- <mat-icon [praxisIcon]="'horizontal_rule'"></mat-icon>
1993
- {{ tx('editor.steps.widgetDivider', 'Divider') }}
1994
- </button>
1995
- </mat-menu>
1996
- </div>
1997
- </div>
1998
- <div cdkDropList (cdkDropListDropped)="dropEditorialBefore($event)" class="widget-list">
1999
- <div class="widget-item editorial-item" *ngFor="let w of editorialBeforeWidgets(step)" cdkDrag>
2000
- <div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
2001
- <div class="info">
2002
- <div class="name">{{ displayWidgetName(w) }}</div>
2003
- <div class="sub">{{ editorialWidgetSummary(w) }}</div>
2004
- <div class="meta">{{ editorialWidgetStatus(w) }}</div>
2005
- <div class="meta" *ngIf="widgetBlockId(w) as blockId">ID: {{ blockId }}</div>
2006
- </div>
2007
- <div class="actions">
2008
- <button mat-icon-button (click)="editWidget(w)" [matTooltip]="tx('editor.steps.editBlock', 'Edit block')">
2009
- <mat-icon [praxisIcon]="'tune'"></mat-icon>
2010
- </button>
2011
- <button mat-icon-button (click)="moveEditorialWidget(w, 'before', 'after')" [matTooltip]="tx('editor.steps.moveAfter', 'Move after the form')">
2012
- <mat-icon [praxisIcon]="'south'"></mat-icon>
2013
- </button>
2014
- <button mat-icon-button (click)="duplicateEditorialWidget(w, 'before')" [matTooltip]="tx('editor.steps.duplicateBlock', 'Duplicate block')">
2015
- <mat-icon [praxisIcon]="'content_copy'"></mat-icon>
2016
- </button>
2017
- <button mat-icon-button color="warn" (click)="removeEditorialWidget(w, 'before')" [matTooltip]="tx('editor.steps.removeBlock', 'Remove block')">
2018
- <mat-icon [praxisIcon]="'delete'"></mat-icon>
2019
- </button>
2020
- </div>
2021
1411
  </div>
2022
- <div class="empty" *ngIf="!editorialBeforeWidgets(step).length">{{ tx('editor.steps.emptyZone', 'No blocks in this zone.') }}</div>
2023
1412
  </div>
1413
+ <mat-form-field appearance="outline" class="w-full rich-content-json">
1414
+ <mat-label>{{ tx('editor.steps.richContentDocument', 'RichContentDocument JSON') }}</mat-label>
1415
+ <textarea
1416
+ matInput
1417
+ rows="12"
1418
+ [ngModel]="richContentJson(step, 'before')"
1419
+ (ngModelChange)="setRichContentJson(step, 'before', $event)"
1420
+ ></textarea>
1421
+ </mat-form-field>
1422
+ <button mat-button color="warn" type="button" (click)="clearRichContent(step, 'before')">
1423
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
1424
+ {{ tx('editor.steps.clearZone', 'Clear zone') }}
1425
+ </button>
2024
1426
  </div>
2025
1427
 
2026
1428
  <div class="zone-column">
2027
1429
  <div class="zone-head">
2028
1430
  <div class="zone-title">{{ tx('editor.steps.zoneAfter', 'After form') }}</div>
2029
1431
  <div class="zone-actions">
2030
- <span class="zone-count">{{ editorialAfterWidgets(step).length }}</span>
2031
- <button mat-stroked-button [matMenuTriggerFor]="afterEditorialMenu">
2032
- <mat-icon [praxisIcon]="'add'"></mat-icon>
2033
- {{ tx('editor.steps.addBlock', 'Add') }}
1432
+ <span class="zone-count">{{ richContentNodeCount(step.stepBlocksAfterForm) }}</span>
1433
+ <button mat-stroked-button type="button" (click)="seedRichContent(step, 'after')">
1434
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
1435
+ {{ tx('editor.steps.seedRichContent', 'Seed') }}
2034
1436
  </button>
2035
- <mat-menu #afterEditorialMenu="matMenu">
2036
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-benefits', 'after')">
2037
- <mat-icon [praxisIcon]="'view_module'"></mat-icon>
2038
- {{ tx('editor.steps.widgetBenefits', 'Benefits grid') }}
2039
- </button>
2040
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-content', 'after')">
2041
- <mat-icon [praxisIcon]="'article'"></mat-icon>
2042
- {{ tx('editor.steps.widgetContent', 'Content block') }}
2043
- </button>
2044
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-inline-notice', 'after')">
2045
- <mat-icon [praxisIcon]="'info'"></mat-icon>
2046
- {{ tx('editor.steps.widgetNotice', 'Inline notice') }}
2047
- </button>
2048
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-divider', 'after')">
2049
- <mat-icon [praxisIcon]="'horizontal_rule'"></mat-icon>
2050
- {{ tx('editor.steps.widgetDivider', 'Divider') }}
2051
- </button>
2052
- </mat-menu>
2053
1437
  </div>
2054
1438
  </div>
2055
- <div cdkDropList (cdkDropListDropped)="dropEditorialAfter($event)" class="widget-list">
2056
- <div class="widget-item editorial-item" *ngFor="let w of editorialAfterWidgets(step)" cdkDrag>
2057
- <div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
2058
- <div class="info">
2059
- <div class="name">{{ displayWidgetName(w) }}</div>
2060
- <div class="sub">{{ editorialWidgetSummary(w) }}</div>
2061
- <div class="meta">{{ editorialWidgetStatus(w) }}</div>
2062
- <div class="meta" *ngIf="widgetBlockId(w) as blockId">ID: {{ blockId }}</div>
2063
- </div>
2064
- <div class="actions">
2065
- <button mat-icon-button (click)="editWidget(w)" [matTooltip]="tx('editor.steps.editBlock', 'Edit block')">
2066
- <mat-icon [praxisIcon]="'tune'"></mat-icon>
2067
- </button>
2068
- <button mat-icon-button (click)="moveEditorialWidget(w, 'after', 'before')" [matTooltip]="tx('editor.steps.moveBefore', 'Move before the form')">
2069
- <mat-icon [praxisIcon]="'north'"></mat-icon>
2070
- </button>
2071
- <button mat-icon-button (click)="duplicateEditorialWidget(w, 'after')" [matTooltip]="tx('editor.steps.duplicateBlock', 'Duplicate block')">
2072
- <mat-icon [praxisIcon]="'content_copy'"></mat-icon>
2073
- </button>
2074
- <button mat-icon-button color="warn" (click)="removeEditorialWidget(w, 'after')" [matTooltip]="tx('editor.steps.removeBlock', 'Remove block')">
2075
- <mat-icon [praxisIcon]="'delete'"></mat-icon>
2076
- </button>
2077
- </div>
2078
- </div>
2079
- <div class="empty" *ngIf="!editorialAfterWidgets(step).length">{{ tx('editor.steps.emptyZone', 'No blocks in this zone.') }}</div>
2080
- </div>
1439
+ <mat-form-field appearance="outline" class="w-full rich-content-json">
1440
+ <mat-label>{{ tx('editor.steps.richContentDocument', 'RichContentDocument JSON') }}</mat-label>
1441
+ <textarea
1442
+ matInput
1443
+ rows="12"
1444
+ [ngModel]="richContentJson(step, 'after')"
1445
+ (ngModelChange)="setRichContentJson(step, 'after', $event)"
1446
+ ></textarea>
1447
+ </mat-form-field>
1448
+ <button mat-button color="warn" type="button" (click)="clearRichContent(step, 'after')">
1449
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
1450
+ {{ tx('editor.steps.clearZone', 'Clear zone') }}
1451
+ </button>
2081
1452
  </div>
2082
1453
  </div>
2083
1454
  </div>
@@ -2409,7 +1780,7 @@ class PraxisStepperConfigEditor {
2409
1780
  </mat-tab>
2410
1781
  </mat-tab-group>
2411
1782
  </div>
2412
- `, isInline: true, styles: [".pdx-editor{display:grid;gap:16px}.pdx-editor .mat-mdc-form-field{width:100%}.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:16px;display:grid;gap:16px;background:var(--md-sys-color-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 78%,transparent);border-radius:16px}.help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.editor-intro{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:4px 2px 2px}.editor-intro-copy{display:grid;gap:6px;max-width:760px}.eyebrow{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);font-weight:600}.editor-title{font-size:22px;line-height:1.15;font-weight:650;color:var(--md-sys-color-on-surface)}.editor-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);line-height:1.45;font-size:14px}.editor-intro-meta{display:flex;gap:8px;align-items:flex-start}.meta-pill{display:grid;gap:2px;min-width:88px;padding:10px 12px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:var(--md-sys-color-surface-container-low)}.meta-label{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--md-sys-color-on-surface-variant)}.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}.settings-group{display:grid;gap:14px;padding:2px 0 4px}.settings-group+.settings-group,.settings-group+.disclosure,.disclosure+.settings-group{padding-top:18px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 60%,transparent)}.group-head{display:grid;gap:4px;max-width:72ch}.inline-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;align-items:start}.inline-action-item{display:grid;gap:8px;align-content:start;min-width:0}.pdx-steps{display:grid;gap:12px;padding:4px 0 2px}.steps-workspace{display:grid;grid-template-columns:minmax(260px,320px) minmax(0,1fr);gap:24px;align-items:start}.pdx-steps-header{display:flex;justify-content:space-between;align-items:center;gap:16px}.pdx-step-list{display:grid;gap:10px}.pdx-step-item{display:grid;grid-template-columns:20px 1fr auto;gap:10px;align-items:start;padding:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 68%,transparent);border-radius:14px;background:var(--md-sys-color-surface);cursor:pointer;outline:none}.pdx-step-item.active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 26%,var(--md-sys-color-surface));box-shadow:none}.pdx-step-item:focus-visible{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.drag-handle{display:flex;align-items:center;color:var(--md-sys-color-on-surface-variant)}.step-summary{display:grid;gap:8px;min-width:0}.step-summary-head{display:grid;gap:2px}.step-summary-index{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--md-sys-color-on-surface-variant);font-weight:600}.step-summary-title{font-size:15px;line-height:1.3;font-weight:600;color:var(--md-sys-color-on-surface)}.step-summary-sub{font-size:12px;color:var(--md-sys-color-on-surface-variant);font-family:var(--md-ref-typeface-plain, monospace);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.step-summary-flags{display:flex;gap:6px;flex-wrap:wrap}.summary-chip{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:600}.summary-chip-warn{background:color-mix(in srgb,var(--md-sys-color-error-container) 60%,transparent);color:var(--md-sys-color-on-error-container)}.pdx-active-step{display:grid;gap:18px;min-width:0}.pdx-active-step .hdr{display:flex;gap:12px;align-items:flex-start;justify-content:space-between}.active-step-head{display:grid;gap:4px}.pdx-content-editor{display:grid;gap:16px}.section{display:grid;gap:12px;padding:0;border:none;border-radius:0;background:transparent}.section-body{display:grid;gap:12px}.section-title{font-weight:600;font-size:15px;color:var(--md-sys-color-on-surface)}.section-subtitle{font-size:13px;color:var(--md-sys-color-on-surface-variant);line-height:1.45}.section-head{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}.editorial-section{padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 68%,transparent)}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.field-span-2{grid-column:span 2}.toggle-group{display:flex;gap:10px;flex-wrap:wrap;padding-top:4px}.disclosure{padding:0 0 12px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 62%,transparent)}.disclosure summary{list-style:none;display:flex;justify-content:space-between;align-items:baseline;gap:16px;cursor:pointer;padding:0;position:relative}.disclosure summary::-webkit-details-marker{display:none}.disclosure summary:after{content:\"expand_more\";font-family:Material Icons;font-size:18px;color:var(--md-sys-color-on-surface-variant);transition:transform .16s ease}.disclosure[open] summary:after{transform:rotate(180deg)}.disclosure summary:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 58%,transparent);outline-offset:4px;border-radius:8px}.disclosure-body{padding-top:12px}.widget-list{display:grid;gap:8px}.widget-item{display:grid;grid-template-columns:20px 1fr auto;align-items:start;gap:12px;padding:10px 0;border:none;border-radius:0;background:transparent;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.widget-item .info{display:grid;gap:2px;min-width:0}.widget-item .info .name{font-weight:600}.widget-item .info .sub{font-size:12px;color:var(--md-sys-color-on-surface-variant);line-height:1.45}.widget-item .info .meta{margin-top:2px;font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:var(--md-ref-typeface-plain, monospace);opacity:.85}.widget-item .actions{display:flex;gap:2px;flex-wrap:nowrap;justify-content:flex-end;align-items:flex-start;opacity:.92}.editorial-item{align-items:start}.editorial-item .actions{justify-content:flex-end}.cta-row{display:flex;gap:8px;align-items:center}.spacer{flex:1 1 auto}.muted{color:var(--md-sys-color-on-surface-variant)}.cta-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;margin-bottom:4px}.cta-row-item{display:grid;gap:10px;padding:12px 0;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.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}.zone-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px}.zone-column{display:grid;gap:10px;min-width:0}.zone-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.zone-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.zone-title{font-weight:600}.zone-column+.zone-column{padding-left:16px;border-left:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.zone-count{font-size:12px;padding:2px 8px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.advanced-list{margin-top:4px}.form-row-card{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.form-row-main{display:flex;align-items:center;gap:12px;min-width:0}.form-row-actions{display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end}.code-card{margin-top:8px}.code-head{font-weight:600;margin-bottom:4px}.code{white-space:pre;overflow:auto;background:var(--md-sys-color-surface-container-highest)}.w-full{grid-column:1 / -1}.min-180{min-width:180px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}.cta-row-item .cta-head{display:grid;grid-template-columns:20px 1fr;align-items:start;gap:10px}.cta-row-item .cta-title{font-weight:600;color:var(--md-sys-color-on-surface)}.cta-row-item .cta-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant);line-height:1.45;margin-top:2px}.cta-row-item .cta-actions{display:flex;gap:8px;align-items:center}.widget-item:last-child,.cta-row-item:last-child,.form-row-card:last-child{border-bottom:none}@media(max-width:900px){.editor-intro{flex-direction:column}.steps-workspace{grid-template-columns:1fr;gap:20px}.pdx-step-item{grid-template-columns:20px 1fr}.pdx-actions{grid-column:1 / -1;justify-self:end}.widget-item{grid-template-columns:20px 1fr}.widget-item .actions{grid-column:1 / -1;justify-content:flex-end}.form-row-card{flex-direction:column;align-items:flex-start}.form-row-actions{justify-content:flex-start}.zone-column+.zone-column{padding-left:0;border-left:none;padding-top:12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}}@media(max-width:640px){.tab-pad{padding:14px}.editor-title{font-size:20px}.pdx-active-step .hdr{align-items:flex-start;flex-direction:column}.detail-grid{grid-template-columns:1fr}.field-span-2{grid-column:auto}.toggle-group{flex-direction:column;align-items:flex-start}.zone-head{align-items:flex-start;flex-direction:column}.zone-actions,.widget-item .actions{justify-content:flex-start}.disclosure summary{flex-direction:column;align-items:flex-start}}\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: "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: MatDialogModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i10.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i10.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i10.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i11.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i11.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"] }] });
1783
+ `, isInline: true, styles: [".pdx-editor{display:grid;gap:16px}.pdx-editor .mat-mdc-form-field{width:100%}.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:16px;display:grid;gap:16px;background:var(--md-sys-color-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 78%,transparent);border-radius:16px}.help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.editor-intro{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:4px 2px 2px}.editor-intro-copy{display:grid;gap:6px;max-width:760px}.eyebrow{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);font-weight:600}.editor-title{font-size:22px;line-height:1.15;font-weight:650;color:var(--md-sys-color-on-surface)}.editor-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);line-height:1.45;font-size:14px}.editor-intro-meta{display:flex;gap:8px;align-items:flex-start}.meta-pill{display:grid;gap:2px;min-width:88px;padding:10px 12px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:var(--md-sys-color-surface-container-low)}.meta-label{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--md-sys-color-on-surface-variant)}.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}.settings-group{display:grid;gap:14px;padding:2px 0 4px}.settings-group+.settings-group,.settings-group+.disclosure,.disclosure+.settings-group{padding-top:18px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 60%,transparent)}.group-head{display:grid;gap:4px;max-width:72ch}.inline-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;align-items:start}.inline-action-item{display:grid;gap:8px;align-content:start;min-width:0}.pdx-steps{display:grid;gap:12px;padding:4px 0 2px}.steps-workspace{display:grid;grid-template-columns:minmax(260px,320px) minmax(0,1fr);gap:24px;align-items:start}.pdx-steps-header{display:flex;justify-content:space-between;align-items:center;gap:16px}.pdx-step-list{display:grid;gap:10px}.pdx-step-item{display:grid;grid-template-columns:20px 1fr auto;gap:10px;align-items:start;padding:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 68%,transparent);border-radius:14px;background:var(--md-sys-color-surface);cursor:pointer;outline:none}.pdx-step-item.active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 26%,var(--md-sys-color-surface));box-shadow:none}.pdx-step-item:focus-visible{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.drag-handle{display:flex;align-items:center;color:var(--md-sys-color-on-surface-variant)}.step-summary{display:grid;gap:8px;min-width:0}.step-summary-head{display:grid;gap:2px}.step-summary-index{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--md-sys-color-on-surface-variant);font-weight:600}.step-summary-title{font-size:15px;line-height:1.3;font-weight:600;color:var(--md-sys-color-on-surface)}.step-summary-sub{font-size:12px;color:var(--md-sys-color-on-surface-variant);font-family:var(--md-ref-typeface-plain, monospace);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.step-summary-flags{display:flex;gap:6px;flex-wrap:wrap}.summary-chip{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:600}.summary-chip-warn{background:color-mix(in srgb,var(--md-sys-color-error-container) 60%,transparent);color:var(--md-sys-color-on-error-container)}.pdx-active-step{display:grid;gap:18px;min-width:0}.pdx-active-step .hdr{display:flex;gap:12px;align-items:flex-start;justify-content:space-between}.active-step-head{display:grid;gap:4px}.pdx-content-editor{display:grid;gap:16px}.section{display:grid;gap:12px;padding:0;border:none;border-radius:0;background:transparent}.section-body{display:grid;gap:12px}.section-title{font-weight:600;font-size:15px;color:var(--md-sys-color-on-surface)}.section-subtitle{font-size:13px;color:var(--md-sys-color-on-surface-variant);line-height:1.45}.section-head{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}.editorial-section{padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 68%,transparent)}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.field-span-2{grid-column:span 2}.toggle-group{display:flex;gap:10px;flex-wrap:wrap;padding-top:4px}.disclosure{padding:0 0 12px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 62%,transparent)}.disclosure summary{list-style:none;display:flex;justify-content:space-between;align-items:baseline;gap:16px;cursor:pointer;padding:0;position:relative}.disclosure summary::-webkit-details-marker{display:none}.disclosure summary:after{content:\"expand_more\";font-family:Material Icons;font-size:18px;color:var(--md-sys-color-on-surface-variant);transition:transform .16s ease}.disclosure[open] summary:after{transform:rotate(180deg)}.disclosure summary:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 58%,transparent);outline-offset:4px;border-radius:8px}.disclosure-body{padding-top:12px}.widget-list{display:grid;gap:8px}.widget-item{display:grid;grid-template-columns:20px 1fr auto;align-items:start;gap:12px;padding:10px 0;border:none;border-radius:0;background:transparent;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.widget-item .info{display:grid;gap:2px;min-width:0}.widget-item .info .name{font-weight:600}.widget-item .info .sub{font-size:12px;color:var(--md-sys-color-on-surface-variant);line-height:1.45}.widget-item .info .meta{margin-top:2px;font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:var(--md-ref-typeface-plain, monospace);opacity:.85}.widget-item .actions{display:flex;gap:2px;flex-wrap:nowrap;justify-content:flex-end;align-items:flex-start;opacity:.92}.editorial-item{align-items:start}.editorial-item .actions{justify-content:flex-end}.cta-row{display:flex;gap:8px;align-items:center}.spacer{flex:1 1 auto}.muted{color:var(--md-sys-color-on-surface-variant)}.cta-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;margin-bottom:4px}.cta-row-item{display:grid;gap:10px;padding:12px 0;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.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}.zone-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px}.zone-column{display:grid;gap:10px;min-width:0}.zone-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.zone-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.zone-title{font-weight:600}.zone-column+.zone-column{padding-left:16px;border-left:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.zone-count{font-size:12px;padding:2px 8px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.advanced-list{margin-top:4px}.form-row-card{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}.form-row-main{display:flex;align-items:center;gap:12px;min-width:0}.form-row-actions{display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end}.code-card{margin-top:8px}.code-head{font-weight:600;margin-bottom:4px}.code{white-space:pre;overflow:auto;background:var(--md-sys-color-surface-container-highest)}.w-full{grid-column:1 / -1}.min-180{min-width:180px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}.cta-row-item .cta-head{display:grid;grid-template-columns:20px 1fr;align-items:start;gap:10px}.cta-row-item .cta-title{font-weight:600;color:var(--md-sys-color-on-surface)}.cta-row-item .cta-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant);line-height:1.45;margin-top:2px}.cta-row-item .cta-actions{display:flex;gap:8px;align-items:center}.widget-item:last-child,.cta-row-item:last-child,.form-row-card:last-child{border-bottom:none}@media(max-width:900px){.editor-intro{flex-direction:column}.steps-workspace{grid-template-columns:1fr;gap:20px}.pdx-step-item{grid-template-columns:20px 1fr}.pdx-actions{grid-column:1 / -1;justify-self:end}.widget-item{grid-template-columns:20px 1fr}.widget-item .actions{grid-column:1 / -1;justify-content:flex-end}.form-row-card{flex-direction:column;align-items:flex-start}.form-row-actions{justify-content:flex-start}.zone-column+.zone-column{padding-left:0;border-left:none;padding-top:12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 56%,transparent)}}@media(max-width:640px){.tab-pad{padding:14px}.editor-title{font-size:20px}.pdx-active-step .hdr{align-items:flex-start;flex-direction:column}.detail-grid{grid-template-columns:1fr}.field-span-2{grid-column:auto}.toggle-group{flex-direction:column;align-items:flex-start}.zone-head{align-items:flex-start;flex-direction:column}.zone-actions,.widget-item .actions{justify-content:flex-start}.disclosure summary{flex-direction:column;align-items:flex-start}}\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: "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: MatDialogModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i10.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i10.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"] }] });
2413
1784
  }
2414
1785
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisStepperConfigEditor, decorators: [{
2415
1786
  type: Component,
@@ -2425,7 +1796,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2425
1796
  DragDropModule,
2426
1797
  MatTooltipModule,
2427
1798
  MatDialogModule,
2428
- MatMenuModule,
2429
1799
  MatTabsModule,
2430
1800
  ], providers: [providePraxisStepperI18n()], template: `
2431
1801
  <div class="pdx-editor">
@@ -2644,114 +2014,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2644
2014
  <div class="zone-head">
2645
2015
  <div class="zone-title">{{ tx('editor.steps.zoneBefore', 'Before form') }}</div>
2646
2016
  <div class="zone-actions">
2647
- <span class="zone-count">{{ editorialBeforeWidgets(step).length }}</span>
2648
- <button mat-stroked-button [matMenuTriggerFor]="beforeEditorialMenu">
2649
- <mat-icon [praxisIcon]="'add'"></mat-icon>
2650
- {{ tx('editor.steps.addBlock', 'Add') }}
2017
+ <span class="zone-count">{{ richContentNodeCount(step.stepBlocksBeforeForm) }}</span>
2018
+ <button mat-stroked-button type="button" (click)="seedRichContent(step, 'before')">
2019
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
2020
+ {{ tx('editor.steps.seedRichContent', 'Seed') }}
2651
2021
  </button>
2652
- <mat-menu #beforeEditorialMenu="matMenu">
2653
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-benefits', 'before')">
2654
- <mat-icon [praxisIcon]="'view_module'"></mat-icon>
2655
- {{ tx('editor.steps.widgetBenefits', 'Benefits grid') }}
2656
- </button>
2657
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-content', 'before')">
2658
- <mat-icon [praxisIcon]="'article'"></mat-icon>
2659
- {{ tx('editor.steps.widgetContent', 'Content block') }}
2660
- </button>
2661
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-inline-notice', 'before')">
2662
- <mat-icon [praxisIcon]="'info'"></mat-icon>
2663
- {{ tx('editor.steps.widgetNotice', 'Inline notice') }}
2664
- </button>
2665
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-divider', 'before')">
2666
- <mat-icon [praxisIcon]="'horizontal_rule'"></mat-icon>
2667
- {{ tx('editor.steps.widgetDivider', 'Divider') }}
2668
- </button>
2669
- </mat-menu>
2670
2022
  </div>
2671
2023
  </div>
2672
- <div cdkDropList (cdkDropListDropped)="dropEditorialBefore($event)" class="widget-list">
2673
- <div class="widget-item editorial-item" *ngFor="let w of editorialBeforeWidgets(step)" cdkDrag>
2674
- <div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
2675
- <div class="info">
2676
- <div class="name">{{ displayWidgetName(w) }}</div>
2677
- <div class="sub">{{ editorialWidgetSummary(w) }}</div>
2678
- <div class="meta">{{ editorialWidgetStatus(w) }}</div>
2679
- <div class="meta" *ngIf="widgetBlockId(w) as blockId">ID: {{ blockId }}</div>
2680
- </div>
2681
- <div class="actions">
2682
- <button mat-icon-button (click)="editWidget(w)" [matTooltip]="tx('editor.steps.editBlock', 'Edit block')">
2683
- <mat-icon [praxisIcon]="'tune'"></mat-icon>
2684
- </button>
2685
- <button mat-icon-button (click)="moveEditorialWidget(w, 'before', 'after')" [matTooltip]="tx('editor.steps.moveAfter', 'Move after the form')">
2686
- <mat-icon [praxisIcon]="'south'"></mat-icon>
2687
- </button>
2688
- <button mat-icon-button (click)="duplicateEditorialWidget(w, 'before')" [matTooltip]="tx('editor.steps.duplicateBlock', 'Duplicate block')">
2689
- <mat-icon [praxisIcon]="'content_copy'"></mat-icon>
2690
- </button>
2691
- <button mat-icon-button color="warn" (click)="removeEditorialWidget(w, 'before')" [matTooltip]="tx('editor.steps.removeBlock', 'Remove block')">
2692
- <mat-icon [praxisIcon]="'delete'"></mat-icon>
2693
- </button>
2694
- </div>
2695
- </div>
2696
- <div class="empty" *ngIf="!editorialBeforeWidgets(step).length">{{ tx('editor.steps.emptyZone', 'No blocks in this zone.') }}</div>
2697
- </div>
2024
+ <mat-form-field appearance="outline" class="w-full rich-content-json">
2025
+ <mat-label>{{ tx('editor.steps.richContentDocument', 'RichContentDocument JSON') }}</mat-label>
2026
+ <textarea
2027
+ matInput
2028
+ rows="12"
2029
+ [ngModel]="richContentJson(step, 'before')"
2030
+ (ngModelChange)="setRichContentJson(step, 'before', $event)"
2031
+ ></textarea>
2032
+ </mat-form-field>
2033
+ <button mat-button color="warn" type="button" (click)="clearRichContent(step, 'before')">
2034
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
2035
+ {{ tx('editor.steps.clearZone', 'Clear zone') }}
2036
+ </button>
2698
2037
  </div>
2699
2038
 
2700
2039
  <div class="zone-column">
2701
2040
  <div class="zone-head">
2702
2041
  <div class="zone-title">{{ tx('editor.steps.zoneAfter', 'After form') }}</div>
2703
2042
  <div class="zone-actions">
2704
- <span class="zone-count">{{ editorialAfterWidgets(step).length }}</span>
2705
- <button mat-stroked-button [matMenuTriggerFor]="afterEditorialMenu">
2706
- <mat-icon [praxisIcon]="'add'"></mat-icon>
2707
- {{ tx('editor.steps.addBlock', 'Add') }}
2043
+ <span class="zone-count">{{ richContentNodeCount(step.stepBlocksAfterForm) }}</span>
2044
+ <button mat-stroked-button type="button" (click)="seedRichContent(step, 'after')">
2045
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
2046
+ {{ tx('editor.steps.seedRichContent', 'Seed') }}
2708
2047
  </button>
2709
- <mat-menu #afterEditorialMenu="matMenu">
2710
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-benefits', 'after')">
2711
- <mat-icon [praxisIcon]="'view_module'"></mat-icon>
2712
- {{ tx('editor.steps.widgetBenefits', 'Benefits grid') }}
2713
- </button>
2714
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-content', 'after')">
2715
- <mat-icon [praxisIcon]="'article'"></mat-icon>
2716
- {{ tx('editor.steps.widgetContent', 'Content block') }}
2717
- </button>
2718
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-inline-notice', 'after')">
2719
- <mat-icon [praxisIcon]="'info'"></mat-icon>
2720
- {{ tx('editor.steps.widgetNotice', 'Inline notice') }}
2721
- </button>
2722
- <button mat-menu-item (click)="addEditorialWidget('praxis-wizard-divider', 'after')">
2723
- <mat-icon [praxisIcon]="'horizontal_rule'"></mat-icon>
2724
- {{ tx('editor.steps.widgetDivider', 'Divider') }}
2725
- </button>
2726
- </mat-menu>
2727
- </div>
2728
- </div>
2729
- <div cdkDropList (cdkDropListDropped)="dropEditorialAfter($event)" class="widget-list">
2730
- <div class="widget-item editorial-item" *ngFor="let w of editorialAfterWidgets(step)" cdkDrag>
2731
- <div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
2732
- <div class="info">
2733
- <div class="name">{{ displayWidgetName(w) }}</div>
2734
- <div class="sub">{{ editorialWidgetSummary(w) }}</div>
2735
- <div class="meta">{{ editorialWidgetStatus(w) }}</div>
2736
- <div class="meta" *ngIf="widgetBlockId(w) as blockId">ID: {{ blockId }}</div>
2737
- </div>
2738
- <div class="actions">
2739
- <button mat-icon-button (click)="editWidget(w)" [matTooltip]="tx('editor.steps.editBlock', 'Edit block')">
2740
- <mat-icon [praxisIcon]="'tune'"></mat-icon>
2741
- </button>
2742
- <button mat-icon-button (click)="moveEditorialWidget(w, 'after', 'before')" [matTooltip]="tx('editor.steps.moveBefore', 'Move before the form')">
2743
- <mat-icon [praxisIcon]="'north'"></mat-icon>
2744
- </button>
2745
- <button mat-icon-button (click)="duplicateEditorialWidget(w, 'after')" [matTooltip]="tx('editor.steps.duplicateBlock', 'Duplicate block')">
2746
- <mat-icon [praxisIcon]="'content_copy'"></mat-icon>
2747
- </button>
2748
- <button mat-icon-button color="warn" (click)="removeEditorialWidget(w, 'after')" [matTooltip]="tx('editor.steps.removeBlock', 'Remove block')">
2749
- <mat-icon [praxisIcon]="'delete'"></mat-icon>
2750
- </button>
2751
- </div>
2752
2048
  </div>
2753
- <div class="empty" *ngIf="!editorialAfterWidgets(step).length">{{ tx('editor.steps.emptyZone', 'No blocks in this zone.') }}</div>
2754
2049
  </div>
2050
+ <mat-form-field appearance="outline" class="w-full rich-content-json">
2051
+ <mat-label>{{ tx('editor.steps.richContentDocument', 'RichContentDocument JSON') }}</mat-label>
2052
+ <textarea
2053
+ matInput
2054
+ rows="12"
2055
+ [ngModel]="richContentJson(step, 'after')"
2056
+ (ngModelChange)="setRichContentJson(step, 'after', $event)"
2057
+ ></textarea>
2058
+ </mat-form-field>
2059
+ <button mat-button color="warn" type="button" (click)="clearRichContent(step, 'after')">
2060
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
2061
+ {{ tx('editor.steps.clearZone', 'Clear zone') }}
2062
+ </button>
2755
2063
  </div>
2756
2064
  </div>
2757
2065
  </div>
@@ -3192,17 +2500,15 @@ const CAPS = [
3192
2500
  { path: 'steps[].form.config.formBlocksAfter', category: 'steps', valueKind: 'object', description: 'RichContentDocument hospedado depois do formulario inteiro da etapa.' },
3193
2501
  { path: 'steps[].form.schemaSource', category: 'steps', valueKind: 'enum', allowedValues: ENUMS['schemaSource'], description: 'Schema source for Dynamic Form.' },
3194
2502
  { path: 'steps[].form.formId', category: 'steps', valueKind: 'string', description: 'Form id for Dynamic Form.' },
3195
- { path: 'steps[].widgets', category: 'steps', valueKind: 'array', description: 'WidgetDefinition list for step content.' },
3196
- { path: 'steps[].widgets[]', category: 'steps', valueKind: 'object', description: 'WidgetDefinition item.' },
3197
- { path: 'steps[].widgets[].id', category: 'steps', valueKind: 'enum', allowedValues: ENUMS['widgetId'], description: 'Component registry id for widget.', safetyNotes: 'Use ids registered in ComponentMetadataRegistry.' },
3198
- { path: 'steps[].widgets[].inputs', category: 'steps', valueKind: 'object', description: 'Inputs bound into the widget instance.' },
3199
- { path: 'steps[].widgets[].outputs', category: 'steps', valueKind: 'object', description: 'Outputs mapped to actions.' },
3200
- { path: 'steps[].widgets[].outputs.[outputName]', category: 'steps', valueKind: 'object', description: 'Output action mapping (ActionDefinition or "emit").', safetyNotes: 'Use string "emit" to forward the output without mapping.' },
3201
- { path: 'steps[].widgets[].bindingOrder', category: 'steps', valueKind: 'array', description: 'Explicit input binding order.' },
3202
- { path: 'steps[].widgets[].bindingOrder[]', category: 'steps', valueKind: 'string', description: 'Input name applied first.' },
2503
+ { path: 'steps[].stepBlocksBeforeForm', category: 'steps', valueKind: 'object', description: 'RichContentDocument rendered before the step form.' },
2504
+ { path: 'steps[].stepBlocksAfterForm', category: 'steps', valueKind: 'object', description: 'RichContentDocument rendered after the step form.' },
2505
+ { path: 'steps[].widgets', category: 'steps', valueKind: 'array', description: 'Advanced host-owned WidgetDefinition list rendered after canonical rich content.', safetyNotes: 'Use only for embedded components that cannot be represented as RichContentDocument.' },
2506
+ { path: 'steps[].widgets[]', category: 'steps', valueKind: 'object', description: 'Advanced WidgetDefinition item.' },
2507
+ { path: 'steps[].widgets[].id', category: 'steps', valueKind: 'enum', allowedValues: ENUMS['widgetId'], description: 'Component registry id for advanced widget.', safetyNotes: 'Use ids registered in ComponentMetadataRegistry. Do not use widgets for step editorial copy/cards.' },
2508
+ { path: 'steps[].widgets[].inputs', category: 'steps', valueKind: 'object', description: 'Inputs bound into the advanced widget instance.' },
3203
2509
  ];
3204
2510
  const STEPPER_AI_CAPABILITIES = {
3205
- version: 'v1.4',
2511
+ version: 'v1.5',
3206
2512
  enums: ENUMS,
3207
2513
  targets: [
3208
2514
  'praxis-stepper',
@@ -3215,7 +2521,8 @@ const STEPPER_AI_CAPABILITIES = {
3215
2521
  'steps[] should be merged by id or label to avoid replacing all steps.',
3216
2522
  'steps[].form.config uses FormConfig (see form catalog) and may include sections/actions/fieldMetadata.',
3217
2523
  'steps[].form.config tambem pode usar a surface editorial do dynamic-form: formBlocksBefore, formBlocksBeforeActions, formBlocksAfter e editorialContext como RichContentDocument.',
3218
- 'steps[].widgets[].id must be a ComponentMetadataRegistry id (see component-registry catalog).',
2524
+ 'Use steps[].stepBlocksBeforeForm and steps[].stepBlocksAfterForm for editorial step content; both are RichContentDocument surfaces rendered by @praxisui/rich-content.',
2525
+ 'steps[].widgets is reserved for advanced embedded components that do not have a canonical rich-content representation.',
3219
2526
  'appearance.tokens is a free-form map; host may validate/sanitize.',
3220
2527
  'When editing steps[].form.config.fieldMetadata, use FieldMetadata base if no controlType catalog exists.',
3221
2528
  ],
@@ -3379,6 +2686,7 @@ class PraxisStepper {
3379
2686
  return undefined;
3380
2687
  } })();
3381
2688
  warnedMissingId = false;
2689
+ emptyStepRichContentDocument = createEmptyRichContentDocument();
3382
2690
  animationDone = new EventEmitter();
3383
2691
  aiAdapter = new StepperAiAdapter(this);
3384
2692
  // Computed getters
@@ -3519,9 +2827,25 @@ class PraxisStepper {
3519
2827
  }
3520
2828
  isStepEmpty(step) {
3521
2829
  const hasForm = !!step.form;
3522
- const hasWidgetsBefore = Array.isArray(step.widgetsBeforeForm) && step.widgetsBeforeForm.length > 0;
2830
+ const hasBlocksBefore = this.hasRichContent(step.stepBlocksBeforeForm);
2831
+ const hasBlocksAfter = this.hasRichContent(step.stepBlocksAfterForm);
3523
2832
  const hasWidgets = Array.isArray(step.widgets) && step.widgets.length > 0;
3524
- return !hasForm && !hasWidgets && !hasWidgetsBefore;
2833
+ return !hasForm && !hasWidgets && !hasBlocksBefore && !hasBlocksAfter;
2834
+ }
2835
+ hasRichContent(document) {
2836
+ return Array.isArray(document?.nodes) && document.nodes.length > 0;
2837
+ }
2838
+ getStepRichContentContext(stepIndex) {
2839
+ const step = this.steps()[stepIndex];
2840
+ return {
2841
+ step: (step ?? {}),
2842
+ stepIndex,
2843
+ stepper: {
2844
+ stepperId: this.stepperId,
2845
+ selectedIndex: this.selectedIndexComputed(),
2846
+ },
2847
+ ...(this.stepperContext ?? {}),
2848
+ };
3525
2849
  }
3526
2850
  openEditor() {
3527
2851
  const cfg = this._config() || { steps: [], orientation: 'horizontal', headerPosition: 'top', linear: false };
@@ -3784,12 +3108,12 @@ class PraxisStepper {
3784
3108
  <ng-template #plainLabel>{{ step.label }}</ng-template>
3785
3109
  </ng-template>
3786
3110
  <div [class]="contentClassList()">
3787
- <ng-container *ngFor="let wd of step.widgetsBeforeForm || []"
3788
- [dynamicWidgetLoader]="wd"
3789
- [context]="stepperContext"
3790
- [strictValidation]="true"
3791
- [autoWireOutputs]="true"
3792
- (widgetEvent)="onChildWidgetEvent(i, wd, $event)"></ng-container>
3111
+ <praxis-rich-content
3112
+ *ngIf="hasRichContent(step.stepBlocksBeforeForm)"
3113
+ [document]="step.stepBlocksBeforeForm || emptyStepRichContentDocument"
3114
+ [context]="getStepRichContentContext(i)"
3115
+ rootClassName="pdx-step-rich-content pdx-step-rich-content--before"
3116
+ ></praxis-rich-content>
3793
3117
  <praxis-dynamic-form *ngIf="step.form as f"
3794
3118
  [resourcePath]="f.resourcePath"
3795
3119
  [resourceId]="$any(f.resourceId)"
@@ -3800,6 +3124,12 @@ class PraxisStepper {
3800
3124
  (formReady)="onFormReady(i, $event)"
3801
3125
  (valueChange)="onFormValueChange(i, $event)"
3802
3126
  ></praxis-dynamic-form>
3127
+ <praxis-rich-content
3128
+ *ngIf="hasRichContent(step.stepBlocksAfterForm)"
3129
+ [document]="step.stepBlocksAfterForm || emptyStepRichContentDocument"
3130
+ [context]="getStepRichContentContext(i)"
3131
+ rootClassName="pdx-step-rich-content pdx-step-rich-content--after"
3132
+ ></praxis-rich-content>
3803
3133
  <ng-container *ngFor="let wd of step.widgets || []"
3804
3134
  [dynamicWidgetLoader]="wd"
3805
3135
  [context]="stepperContext"
@@ -3881,11 +3211,11 @@ class PraxisStepper {
3881
3211
  >
3882
3212
  <mat-icon fontIcon="edit"></mat-icon>
3883
3213
  </button>
3884
- `, isInline: true, styles: [":host{display:block;position:relative}.praxis-stepper{width:100%;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;padding:12px;box-shadow:var(--mat-elevation-level1)}.praxis-stepper.pdx-stepper-ft-light{background:transparent;border:none;border-radius:0;padding:0;box-shadow:none}:host(.density-compact) ::ng-deep .mat-step-header{min-height:36px}:host(.density-compact) .pdx-step-actions{padding-top:4px;gap:6px}:host(.density-comfortable) ::ng-deep .mat-step-header{min-height:44px}.pdx-step-content{padding:12px 4px 8px;color:var(--md-sys-color-on-surface)}.pdx-step-actions{display:flex;gap:8px;padding-top:8px}::ng-deep .praxis-stepper .mat-step-header{border-radius:999px;margin:4px 0;color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-label{color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-label.mat-step-label-selected{color:var(--md-sys-color-on-surface);font-weight:600}::ng-deep .praxis-stepper .mat-step-header.cdk-keyboard-focused,::ng-deep .praxis-stepper .mat-step-header.cdk-program-focused{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}::ng-deep .praxis-stepper .mat-step-icon{background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-icon-selected{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}::ng-deep .praxis-stepper .mat-step-icon-state-done{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}::ng-deep .praxis-stepper .mat-step-icon-state-error{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}::ng-deep .praxis-stepper .mat-stepper-horizontal-line{border-top-color:var(--md-sys-color-outline-variant)}::ng-deep .praxis-stepper .mat-stepper-vertical-line:before{border-left-color:var(--md-sys-color-outline-variant)}::ng-deep .pdx-stepper-ft-light .mat-stepper-horizontal{background:transparent;--mat-stepper-header-hover-state-layer-color: color-mix( in srgb, var(--md-sys-color-on-surface) 8%, transparent );--mat-stepper-header-focus-state-layer-color: transparent;--mat-stepper-header-hover-state-layer-shape: 14px;--mat-stepper-header-focus-state-layer-shape: 14px}::ng-deep .pdx-stepper-ft-light .mat-step-header{margin:0}::ng-deep .pdx-stepper-ft-light .mat-step-header .mat-step-header-ripple{display:none}::ng-deep .ft-stepper{background:transparent}.ft-stepper-content{padding:0}.ft-stepper-content .form-section{border:none;padding:0;background:transparent}.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}.stepper-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\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: "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", "initialValue", "editorialContext", "mode", "config", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3214
+ `, isInline: true, styles: [":host{display:block;position:relative}.praxis-stepper{width:100%;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;padding:12px;box-shadow:var(--mat-elevation-level1)}.praxis-stepper.pdx-stepper-ft-light{background:transparent;border:none;border-radius:0;padding:0;box-shadow:none}:host(.density-compact) ::ng-deep .mat-step-header{min-height:36px}:host(.density-compact) .pdx-step-actions{padding-top:4px;gap:6px}:host(.density-comfortable) ::ng-deep .mat-step-header{min-height:44px}.pdx-step-content{padding:12px 4px 8px;color:var(--md-sys-color-on-surface)}.pdx-step-actions{display:flex;gap:8px;padding-top:8px}::ng-deep .praxis-stepper .mat-step-header{border-radius:999px;margin:4px 0;color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-label{color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-label.mat-step-label-selected{color:var(--md-sys-color-on-surface);font-weight:600}::ng-deep .praxis-stepper .mat-step-header.cdk-keyboard-focused,::ng-deep .praxis-stepper .mat-step-header.cdk-program-focused{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}::ng-deep .praxis-stepper .mat-step-icon{background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}::ng-deep .praxis-stepper .mat-step-icon-selected{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}::ng-deep .praxis-stepper .mat-step-icon-state-done{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}::ng-deep .praxis-stepper .mat-step-icon-state-error{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}::ng-deep .praxis-stepper .mat-stepper-horizontal-line{border-top-color:var(--md-sys-color-outline-variant)}::ng-deep .praxis-stepper .mat-stepper-vertical-line:before{border-left-color:var(--md-sys-color-outline-variant)}::ng-deep .pdx-stepper-ft-light .mat-stepper-horizontal{background:transparent;--mat-stepper-header-hover-state-layer-color: color-mix( in srgb, var(--md-sys-color-on-surface) 8%, transparent );--mat-stepper-header-focus-state-layer-color: transparent;--mat-stepper-header-hover-state-layer-shape: 14px;--mat-stepper-header-focus-state-layer-shape: 14px}::ng-deep .pdx-stepper-ft-light .mat-step-header{margin:0}::ng-deep .pdx-stepper-ft-light .mat-step-header .mat-step-header-ripple{display:none}::ng-deep .ft-stepper{background:transparent}.ft-stepper-content{padding:0}.ft-stepper-content .form-section{border:none;padding:0;background:transparent}.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}.stepper-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\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: "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", "initialValue", "editorialContext", "mode", "config", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3885
3215
  }
3886
3216
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisStepper, decorators: [{
3887
3217
  type: Component,
3888
- args: [{ selector: 'praxis-stepper', standalone: true, imports: [CommonModule, ReactiveFormsModule, MatStepperModule, MatButtonModule, MatIconModule, PraxisIconDirective, PraxisDynamicForm, DynamicWidgetLoaderDirective, EmptyStateCardComponent, PraxisAiAssistantComponent], host: {
3218
+ args: [{ selector: 'praxis-stepper', standalone: true, imports: [CommonModule, ReactiveFormsModule, MatStepperModule, MatButtonModule, MatIconModule, PraxisIconDirective, PraxisDynamicForm, DynamicWidgetLoaderDirective, EmptyStateCardComponent, PraxisAiAssistantComponent, PraxisRichContent], host: {
3889
3219
  '[class]': 'densityClass()'
3890
3220
  }, template: `
3891
3221
  <div class="stepper-ai-assistant" *ngIf="enableCustomization">
@@ -3942,12 +3272,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3942
3272
  <ng-template #plainLabel>{{ step.label }}</ng-template>
3943
3273
  </ng-template>
3944
3274
  <div [class]="contentClassList()">
3945
- <ng-container *ngFor="let wd of step.widgetsBeforeForm || []"
3946
- [dynamicWidgetLoader]="wd"
3947
- [context]="stepperContext"
3948
- [strictValidation]="true"
3949
- [autoWireOutputs]="true"
3950
- (widgetEvent)="onChildWidgetEvent(i, wd, $event)"></ng-container>
3275
+ <praxis-rich-content
3276
+ *ngIf="hasRichContent(step.stepBlocksBeforeForm)"
3277
+ [document]="step.stepBlocksBeforeForm || emptyStepRichContentDocument"
3278
+ [context]="getStepRichContentContext(i)"
3279
+ rootClassName="pdx-step-rich-content pdx-step-rich-content--before"
3280
+ ></praxis-rich-content>
3951
3281
  <praxis-dynamic-form *ngIf="step.form as f"
3952
3282
  [resourcePath]="f.resourcePath"
3953
3283
  [resourceId]="$any(f.resourceId)"
@@ -3958,6 +3288,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3958
3288
  (formReady)="onFormReady(i, $event)"
3959
3289
  (valueChange)="onFormValueChange(i, $event)"
3960
3290
  ></praxis-dynamic-form>
3291
+ <praxis-rich-content
3292
+ *ngIf="hasRichContent(step.stepBlocksAfterForm)"
3293
+ [document]="step.stepBlocksAfterForm || emptyStepRichContentDocument"
3294
+ [context]="getStepRichContentContext(i)"
3295
+ rootClassName="pdx-step-rich-content pdx-step-rich-content--after"
3296
+ ></praxis-rich-content>
3961
3297
  <ng-container *ngFor="let wd of step.widgets || []"
3962
3298
  [dynamicWidgetLoader]="wd"
3963
3299
  [context]="stepperContext"
@@ -4346,7 +3682,7 @@ const PRAXIS_WIZARD_CONTENT_METADATA = {
4346
3682
  selector: 'praxis-wizard-content-block',
4347
3683
  component: PraxisWizardContentBlockComponent,
4348
3684
  friendlyName: 'Wizard Content Block',
4349
- description: 'Bloco editorial legado do wizard com título/subtítulo/texto. Está formalmente em deprecação para novo authoring e converge para widget:rich-text-block, mas continua suportado para leitura e edição de configurações existentes neste ciclo.',
3685
+ description: 'Bloco editorial legado do wizard com título/subtítulo/texto. Está formalmente em deprecação para novo authoring e converge para RichContentDocument em stepBlocksBeforeForm/stepBlocksAfterForm, mas continua suportado para leitura e edição de configurações existentes neste ciclo.',
4350
3686
  icon: 'article',
4351
3687
  inputs: [
4352
3688
  { name: 'blockId', type: 'string', label: 'Block ID legado', description: 'Identificador operacional legado do wizard, usado hoje para telemetria/e2e e compatibilidade com editor/adapter.' },
@@ -4695,23 +4031,6 @@ const FT_WIZARD_JSON = `{
4695
4031
  }`;
4696
4032
  const FT_WIZARD_CONFIG = JSON.parse(FT_WIZARD_JSON);
4697
4033
 
4698
- const defaultCardSkin = {
4699
- type: 'elevated',
4700
- radius: '12px',
4701
- border: '1px solid var(--md-sys-color-outline-variant, #e0e0e0)',
4702
- shadow: '0 1px 2px rgba(0,0,0,0.06)',
4703
- };
4704
- const defaultCardLayout = {
4705
- variant: 'cards',
4706
- density: 'comfortable',
4707
- lines: 2,
4708
- model: 'standard',
4709
- dividers: 'none',
4710
- };
4711
- const defaultCardTemplating = {
4712
- primary: { type: 'text', expr: '${item.title}', class: 'ft-card-title' },
4713
- secondary: { type: 'text', expr: '${item.description}', class: 'ft-card-desc' },
4714
- };
4715
4034
  function buildStepperConfig(wizard) {
4716
4035
  const steps = wizard.steps.map((step) => buildStep(step, wizard.id));
4717
4036
  return {
@@ -4733,19 +4052,17 @@ function buildStepperConfig(wizard) {
4733
4052
  }
4734
4053
  function buildStep(step, wizardId) {
4735
4054
  const blocks = buildBlocks(mergeBlocks(step), wizardId, step.id);
4736
- const beforeWidgets = [
4055
+ const beforeNodes = [
4737
4056
  ...blocks.before,
4738
- ...(step.cards?.length ? [buildCardsWidget(step, wizardId)] : []),
4057
+ ...buildCardNodes(step),
4739
4058
  ];
4740
- const widgetsBeforeForm = beforeWidgets.length ? beforeWidgets : undefined;
4741
- const widgetsAfterForm = blocks.after.length ? blocks.after : undefined;
4742
4059
  return {
4743
4060
  id: step.id,
4744
4061
  label: step.label,
4745
4062
  description: step.subtitle || step.description,
4746
4063
  optional: step.optional,
4747
- widgetsBeforeForm,
4748
- widgets: widgetsAfterForm,
4064
+ stepBlocksBeforeForm: toDocument(beforeNodes),
4065
+ stepBlocksAfterForm: toDocument(blocks.after),
4749
4066
  form: step.form
4750
4067
  ? {
4751
4068
  formId: step.form.formId || `${wizardId}:${step.id}`,
@@ -4755,22 +4072,16 @@ function buildStep(step, wizardId) {
4755
4072
  : undefined,
4756
4073
  };
4757
4074
  }
4758
- function buildCardsWidget(step, wizardId) {
4759
- const cfg = {
4760
- id: `${wizardId}:${step.id}:cards`,
4761
- dataSource: { data: step.cards || [] },
4762
- layout: { ...defaultCardLayout },
4763
- skin: { ...defaultCardSkin },
4764
- templating: { ...defaultCardTemplating },
4765
- ui: { showSearch: false, showSort: false, showRange: false },
4766
- };
4767
- return {
4768
- id: 'praxis-list',
4769
- inputs: {
4770
- listId: cfg.id,
4771
- config: cfg,
4772
- },
4773
- };
4075
+ function buildCardNodes(step) {
4076
+ return (step.cards ?? []).map((card, index) => ({
4077
+ type: 'card',
4078
+ id: `${step.id}:card:${index}`,
4079
+ title: card.title,
4080
+ content: [
4081
+ ...(card.icon ? [{ type: 'icon', icon: card.icon, ariaLabel: card.title }] : []),
4082
+ ...(card.description ? [{ type: 'text', text: card.description }] : []),
4083
+ ],
4084
+ }));
4774
4085
  }
4775
4086
  function mergeBlocks(step) {
4776
4087
  const out = [];
@@ -4800,7 +4111,7 @@ function buildBlocks(blocks, wizardId, stepId) {
4800
4111
  const after = [];
4801
4112
  blocks.forEach((block, index) => {
4802
4113
  const placement = block.placement ?? 'beforeForm';
4803
- const def = toWidget(block, wizardId, stepId, index);
4114
+ const def = toRichNode(block, wizardId, stepId, index);
4804
4115
  if (!def)
4805
4116
  return;
4806
4117
  if (placement === 'afterForm')
@@ -4816,57 +4127,76 @@ function resolveBlockRuntimeId(block, wizardId, stepId, index) {
4816
4127
  ? `${wizardId}:${stepId}:block:${explicitId}`
4817
4128
  : `${wizardId}:${stepId}:block:${index}`;
4818
4129
  }
4819
- function toWidget(block, wizardId, stepId, index) {
4130
+ function toRichNode(block, wizardId, stepId, index) {
4820
4131
  const baseId = resolveBlockRuntimeId(block, wizardId, stepId, index);
4821
4132
  switch (block.type) {
4822
4133
  case 'benefitsGrid':
4823
4134
  return {
4824
- id: 'praxis-wizard-benefits',
4825
- inputs: {
4826
- blockId: baseId,
4827
- title: block.title,
4828
- items: block.items,
4829
- columns: block.columns,
4830
- boxed: block.boxed,
4831
- },
4135
+ type: 'card',
4136
+ id: baseId,
4137
+ title: block.title,
4138
+ className: block.boxed === false ? 'pdx-wizard-benefits pdx-wizard-benefits--plain' : 'pdx-wizard-benefits',
4139
+ content: block.items.flatMap((item, itemIndex) => {
4140
+ const nodes = [
4141
+ { type: 'text', text: item.title, id: `${baseId}:title:${itemIndex}` },
4142
+ ];
4143
+ if (item.icon) {
4144
+ nodes.unshift({ type: 'icon', icon: item.icon, ariaLabel: item.title, id: `${baseId}:icon:${itemIndex}` });
4145
+ }
4146
+ if (item.text) {
4147
+ nodes.push({ type: 'text', text: item.text, id: `${baseId}:text:${itemIndex}` });
4148
+ }
4149
+ return nodes;
4150
+ }),
4832
4151
  };
4833
4152
  case 'contentBlock':
4834
4153
  return {
4835
- id: 'widget:rich-text-block',
4836
- inputs: {
4837
- instanceId: baseId,
4838
- title: block.title,
4839
- subtitle: block.subtitle,
4840
- contentFormat: 'plain',
4841
- content: joinContentParts(block.text, block.caption),
4842
- },
4154
+ type: 'card',
4155
+ id: baseId,
4156
+ title: block.title,
4157
+ subtitle: block.subtitle,
4158
+ content: textNodes(baseId, block.text, block.caption),
4843
4159
  };
4844
4160
  case 'inlineNotice':
4845
4161
  return {
4846
- id: 'praxis-wizard-inline-notice',
4847
- inputs: {
4848
- blockId: baseId,
4849
- text: block.text,
4850
- tone: block.tone,
4851
- },
4162
+ type: 'compose',
4163
+ id: baseId,
4164
+ gap: 'sm',
4165
+ items: [
4166
+ { type: 'icon', icon: block.tone === 'warning' ? 'warning' : 'info', ariaLabel: block.tone ?? 'info' },
4167
+ { type: 'text', text: block.text },
4168
+ ],
4852
4169
  };
4853
4170
  case 'divider':
4854
4171
  return {
4855
- id: 'praxis-wizard-divider',
4856
- inputs: {
4857
- blockId: baseId,
4858
- label: block.label ?? '',
4859
- },
4172
+ type: 'text',
4173
+ id: baseId,
4174
+ text: block.label ?? '',
4175
+ className: 'pdx-wizard-divider',
4860
4176
  };
4861
4177
  default:
4862
4178
  return null;
4863
4179
  }
4864
4180
  }
4865
- function joinContentParts(text, caption) {
4866
- const parts = [text, caption]
4181
+ function textNodes(baseId, text, caption) {
4182
+ return [text, caption]
4867
4183
  .map((value) => (typeof value === 'string' ? value.trim() : ''))
4868
- .filter(Boolean);
4869
- return parts.join('\n\n');
4184
+ .filter(Boolean)
4185
+ .map((value, index) => ({
4186
+ type: 'text',
4187
+ id: `${baseId}:text:${index}`,
4188
+ text: value,
4189
+ }));
4190
+ }
4191
+ function toDocument(nodes) {
4192
+ if (!nodes.length) {
4193
+ return null;
4194
+ }
4195
+ return {
4196
+ kind: 'praxis.rich-content',
4197
+ version: '1.0.0',
4198
+ nodes,
4199
+ };
4870
4200
  }
4871
4201
 
4872
4202
  class PraxisWizardCtaBarComponent {