@praxisui/dynamic-form 9.0.5-rc.29 → 9.0.5-rc.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -200,11 +200,19 @@ Dense operational forms should keep DTO `hint`/`helpText` intact and use `FormCo
200
200
  Submit failures published in the Praxis `RestApiResponse.errors` envelope are materialized by the
201
201
  runtime. A safe public `message` is shown to the user, while an error with a `target` matching a
202
202
  form control is also attached to that control and marked as touched. Existing client validators
203
- are preserved, and only the previous server error is cleared before a new submit attempt. Targets
204
- may use Angular dotted paths or JSON Pointer syntax. Unknown targets remain form-level feedback;
205
- the client does not guess a field from error text, codes, keywords, or private database details.
206
- Generic network, validation, server, and save fallbacks use the platform i18n keys under
207
- `global.submitError.*`; backend-owned business messages remain authoritative.
203
+ are preserved. Each field-owned server error is removed as soon as that field changes, without
204
+ clearing client validators or errors on other controls; all remaining server errors are cleared
205
+ before the next submit attempt and after a successful submit. Targets may use Angular dotted paths
206
+ or JSON Pointer syntax. Unknown or targetless details remain form-level feedback; the client does
207
+ not guess a field from error text, codes, keywords, or private database details.
208
+
209
+ Structured details and public backend messages are trusted only for known HTTP `4xx` responses.
210
+ Network failures, `5xx` responses, and responses without a trustworthy status fail closed to the
211
+ localized platform messages under `global.submitError.*`, even when their payload contains
212
+ `message` or `errors`. For a governed `4xx` response with structured details, the backend-owned
213
+ business message has precedence over generic copy configured in `messages`. Without structured
214
+ details, configured create/update/custom submit copy remains authoritative and the normalized
215
+ platform fallback is used only when no configured copy exists.
208
216
 
209
217
  Example backend-owned `x-ui` metadata:
210
218
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-16T01:02:42.425Z",
3
+ "generatedAt": "2026-08-17T01:10:49.556Z",
4
4
  "packageName": "@praxisui/dynamic-form",
5
- "packageVersion": "9.0.5-rc.29",
5
+ "packageVersion": "9.0.5-rc.30",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -1149,6 +1149,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
1149
1149
  'layout.defaultSectionTitle': 'Informações',
1150
1150
  'actions.invalidRequired': 'Preencha os campos obrigatórios para continuar.',
1151
1151
  'actions.invalidRequiredWithFields': 'Preencha: {{fields}}.',
1152
+ 'actions.submitErrorTitle': 'Erro ao salvar',
1152
1153
  'runtime.reactiveDetermination.error': 'Não foi possível atualizar os campos relacionados. Revise os dados ou tente novamente.',
1153
1154
  'runtime.reactiveDetermination.submitPending': 'Aguarde a atualização dos campos relacionados antes de salvar.',
1154
1155
  'runtime.reactiveDetermination.submitTimeout': 'Os campos relacionados não estabilizaram a tempo. Revise os dados e tente salvar novamente.',
@@ -1648,6 +1649,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
1648
1649
  'layout.defaultSectionTitle': 'Information',
1649
1650
  'actions.invalidRequired': 'Complete the required fields to continue.',
1650
1651
  'actions.invalidRequiredWithFields': 'Complete: {{fields}}.',
1652
+ 'actions.submitErrorTitle': 'Save error',
1651
1653
  'config.tabs.mobileLabel': 'Editor section',
1652
1654
  'config.tabs.general': 'General',
1653
1655
  'config.tabs.schema': 'Schema Verification',
@@ -2276,6 +2278,7 @@ class PraxisFormActionsComponent {
2276
2278
  invalidSubmitHintCacheSignature = null;
2277
2279
  invalidSubmitHintCacheValue = '';
2278
2280
  invalidSubmitHint = this.i18n.t('actions.invalidRequired', undefined, 'Preencha os campos obrigatórios para continuar.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE);
2281
+ submitErrorTitle = this.i18n.t('actions.submitErrorTitle', undefined, 'Erro ao salvar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE);
2279
2282
  /** Configuration object describing form action buttons */
2280
2283
  actions;
2281
2284
  /** Whether the actions are rendered inside an editorial visual context */
@@ -2627,7 +2630,7 @@ class PraxisFormActionsComponent {
2627
2630
  return actionId === 'cancel' || actionId === 'reset';
2628
2631
  }
2629
2632
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFormActionsComponent, deps: [{ token: i1$1.KeyboardShortcutService }], target: i0.ɵɵFactoryTarget.Component });
2630
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisFormActionsComponent, isStandalone: true, selector: "praxis-form-actions", inputs: { actions: "actions", editorialVisualContext: "editorialVisualContext", isSubmitting: "isSubmitting", formIsValid: "formIsValid", submitError: "submitError", invalidRequiredFieldLabels: "invalidRequiredFieldLabels", suppressInvalidSubmitHint: "suppressInvalidSubmitHint", formId: "formId", actionOverrides: "actionOverrides" }, outputs: { action: "action" }, providers: [providePraxisI18nConfig(PRAXIS_DYNAMIC_FORM_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"form-actions\"\n [ngClass]=\"viewClassMap\"\n [ngStyle]=\"actions?.containerStyles\"\n>\n @if (submitError) {\n <div class=\"alert alert--error\" role=\"alert\" aria-live=\"polite\">\n <mat-icon class=\"alert__icon\" aria-hidden=\"true\">error_outline</mat-icon>\n <div>\n <h4 class=\"alert__title\">Erro ao salvar</h4>\n <p class=\"alert__text\">{{ submitError }}</p>\n </div>\n </div>\n }\n @if (shouldShowInvalidSubmitHint()) {\n <div class=\"form-actions__hint\" role=\"status\" aria-live=\"polite\">\n <mat-icon class=\"form-actions__hint-icon\" aria-hidden=\"true\">info</mat-icon>\n <span>{{ getInvalidSubmitHint() }}</span>\n </div>\n }\n <div class=\"desktop-actions\">\n @if (isSplitLayout()) {\n <div class=\"desktop-actions-group secondary-group\">\n @for (button of getSecondaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n <div class=\"desktop-actions-group primary-group\">\n @for (button of getPrimaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n } @else {\n @for (button of getActionButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n }\n </div>\n @if (actions?.mobile?.collapseToMenu) {\n <div class=\"mobile-actions\">\n @for (button of getVisibleButtons(); track button.id) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n @if (getCollapsedButtons().length > 0) {\n <button\n praxisIconButton=\"more_vert\"\n size=\"compact\"\n [matMenuTriggerFor]=\"actionsMenu\"\n aria-label=\"More actions\"\n >\n </button>\n <mat-menu #actionsMenu=\"matMenu\">\n @for (button of getCollapsedButtons(); track button.id) {\n <button\n mat-menu-item\n (click)=\"onActionButtonClick(button, $event)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\"\n >\n @if (button.icon) { <mat-icon [praxisIcon]=\"button.icon\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.form-actions{display:flex;justify-content:flex-end;align-items:center;gap:.75rem;padding:1rem 1.1rem;z-index:1;font-family:inherit;border-radius:18px;background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%));border:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.with-divider{border-top:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}:host([data-actions-placement=afterSections]) .form-actions{margin:0;border-right:0;border-bottom:0;border-left:0;border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px);background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%))}:host([data-actions-placement=afterSections]) .form-actions.with-divider{border-top-color:var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.editorial-visual-context{font-family:var(--editorial-body-font-family, inherit)}.form-actions.editorial-visual-context.with-divider{border-top:var(--editorial-card-border-width, 1px) solid color-mix(in srgb,var(--editorial-border-color, var(--md-sys-color-outline-variant)) 72%,transparent)}.form-actions.sticky{position:sticky;bottom:0}.form-actions.position-left{justify-content:flex-start}.form-actions.position-center{justify-content:center}.form-actions.position-right{justify-content:flex-end}.form-actions.position-justified,.form-actions.position-split{justify-content:space-between}.form-actions.orientation-vertical .desktop-actions{display:flex;flex-direction:column}.form-actions.orientation-horizontal .desktop-actions{display:flex;flex-direction:row}.form-actions.spacing-compact{gap:.25rem}.form-actions.spacing-normal{gap:.5rem}.form-actions.spacing-spacious{gap:1rem}.form-actions.spacing-compact .desktop-actions{gap:.25rem}.form-actions.spacing-normal .desktop-actions{gap:.5rem}.form-actions.spacing-spacious .desktop-actions{gap:1rem}.form-actions.loading{pointer-events:none;opacity:.7}.mobile-actions{display:none}.desktop-actions{display:flex;flex-wrap:wrap;gap:.5rem}.form-actions__hint{display:inline-flex;align-items:center;gap:.45rem;max-width:min(100%,32rem);padding:.5rem .65rem;border-radius:8px;font-size:.84rem;line-height:1.25;color:var(--md-sys-color-on-secondary-container);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-secondary-container) 82%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary-container) 44%,transparent));border:1px solid color-mix(in srgb,var(--md-sys-color-secondary) 22%,transparent)}.form-actions__hint-icon{flex:0 0 auto;font-size:18px;width:18px;height:18px;line-height:18px}.form-actions.position-split .desktop-actions{width:100%;justify-content:space-between;align-items:center}.desktop-actions-group{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.desktop-actions-group.primary-group{justify-content:flex-end}.desktop-actions-group.secondary-group{opacity:.94}.form-actions .mdc-button{letter-spacing:.01em;border-radius:999px}.form-actions.editorial-visual-context .mdc-button{border-radius:var(--editorial-button-radius, 999px);font-family:var(--editorial-body-font-family, inherit)}.form-actions .mdc-button.size-small{min-height:36px;padding-inline:12px;font-size:.82rem}.form-actions .mdc-button.size-medium{min-height:44px;padding-inline:18px}.form-actions .mdc-button.size-large{min-height:50px;padding-inline:24px;font-size:.98rem}.form-actions .mat-mdc-raised-button,.form-actions .mat-mdc-unelevated-button{box-shadow:none}.form-actions .mat-mdc-raised-button:not([disabled]),.form-actions .mat-mdc-unelevated-button:not([disabled]){--mat-button-protected-container-color: var(--md-sys-color-primary);--mat-button-protected-label-text-color: var(--md-sys-color-on-primary);--mat-button-filled-container-color: var(--md-sys-color-primary);--mat-button-filled-label-text-color: var(--md-sys-color-on-primary);--mdc-protected-button-container-color: var(--md-sys-color-primary);--mdc-protected-button-label-text-color: var(--md-sys-color-on-primary);--mdc-filled-button-container-color: var(--md-sys-color-primary);--mdc-filled-button-label-text-color: var(--md-sys-color-on-primary)}.form-actions .mat-mdc-raised-button[disabled],.form-actions .mat-mdc-unelevated-button[disabled]{opacity:.55}.form-actions .mat-mdc-outlined-button,.form-actions .mat-mdc-button-base.mat-mdc-outlined-button{background:transparent}.form-actions.editorial-visual-context .mat-mdc-raised-button,.form-actions.editorial-visual-context .mat-mdc-unelevated-button{--mat-button-protected-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-protected-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mat-button-filled-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-filled-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-protected-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-protected-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-filled-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-filled-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));box-shadow:var(--editorial-floating-shadow, var(--editorial-card-shadow, none))}.form-actions.editorial-visual-context .mat-mdc-outlined-button{--mdc-outlined-button-outline-color: var(--editorial-border-color, var(--md-sys-color-outline-variant));--mdc-outlined-button-label-text-color: var( --editorial-cta-secondary-text, var(--editorial-text-primary, var(--md-sys-color-on-surface)) );background:color-mix(in srgb,var(--editorial-cta-secondary, var(--editorial-surface-secondary, var(--md-sys-color-surface-container-low))) 72%,transparent)}.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-primary,.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-accent{--mdc-outlined-button-label-text-color: var(--editorial-cta-primary, var(--md-sys-color-primary));background:transparent}.form-actions button .mat-icon{font-size:20px;width:20px;height:20px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.form-actions button .mat-icon+span{margin-left:8px}@media(max-width:768px){.form-actions{padding:.9rem;border-radius:16px}:host([data-actions-placement=afterSections]) .form-actions{border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px)}.form-actions.mobile-menu-active .desktop-actions{display:none}.form-actions.mobile-menu-active .mobile-actions{display:flex;align-items:center;gap:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.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: i3.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i6.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: i6.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i6.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
2633
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisFormActionsComponent, isStandalone: true, selector: "praxis-form-actions", inputs: { actions: "actions", editorialVisualContext: "editorialVisualContext", isSubmitting: "isSubmitting", formIsValid: "formIsValid", submitError: "submitError", invalidRequiredFieldLabels: "invalidRequiredFieldLabels", suppressInvalidSubmitHint: "suppressInvalidSubmitHint", formId: "formId", actionOverrides: "actionOverrides" }, outputs: { action: "action" }, providers: [providePraxisI18nConfig(PRAXIS_DYNAMIC_FORM_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"form-actions\"\n [ngClass]=\"viewClassMap\"\n [ngStyle]=\"actions?.containerStyles\"\n>\n @if (submitError) {\n <div class=\"alert alert--error\" role=\"alert\" aria-live=\"polite\">\n <mat-icon class=\"alert__icon\" aria-hidden=\"true\">error_outline</mat-icon>\n <div>\n <h4 class=\"alert__title\">{{ submitErrorTitle }}</h4>\n <p class=\"alert__text\">{{ submitError }}</p>\n </div>\n </div>\n }\n @if (shouldShowInvalidSubmitHint()) {\n <div class=\"form-actions__hint\" role=\"status\" aria-live=\"polite\">\n <mat-icon class=\"form-actions__hint-icon\" aria-hidden=\"true\">info</mat-icon>\n <span>{{ getInvalidSubmitHint() }}</span>\n </div>\n }\n <div class=\"desktop-actions\">\n @if (isSplitLayout()) {\n <div class=\"desktop-actions-group secondary-group\">\n @for (button of getSecondaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n <div class=\"desktop-actions-group primary-group\">\n @for (button of getPrimaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n } @else {\n @for (button of getActionButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n }\n </div>\n @if (actions?.mobile?.collapseToMenu) {\n <div class=\"mobile-actions\">\n @for (button of getVisibleButtons(); track button.id) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n @if (getCollapsedButtons().length > 0) {\n <button\n praxisIconButton=\"more_vert\"\n size=\"compact\"\n [matMenuTriggerFor]=\"actionsMenu\"\n aria-label=\"More actions\"\n >\n </button>\n <mat-menu #actionsMenu=\"matMenu\">\n @for (button of getCollapsedButtons(); track button.id) {\n <button\n mat-menu-item\n (click)=\"onActionButtonClick(button, $event)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\"\n >\n @if (button.icon) { <mat-icon [praxisIcon]=\"button.icon\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.form-actions{display:flex;justify-content:flex-end;align-items:center;gap:.75rem;padding:1rem 1.1rem;z-index:1;font-family:inherit;border-radius:18px;background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%));border:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.with-divider{border-top:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}:host([data-actions-placement=afterSections]) .form-actions{margin:0;border-right:0;border-bottom:0;border-left:0;border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px);background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%))}:host([data-actions-placement=afterSections]) .form-actions.with-divider{border-top-color:var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.editorial-visual-context{font-family:var(--editorial-body-font-family, inherit)}.form-actions.editorial-visual-context.with-divider{border-top:var(--editorial-card-border-width, 1px) solid color-mix(in srgb,var(--editorial-border-color, var(--md-sys-color-outline-variant)) 72%,transparent)}.form-actions.sticky{position:sticky;bottom:0}.form-actions.position-left{justify-content:flex-start}.form-actions.position-center{justify-content:center}.form-actions.position-right{justify-content:flex-end}.form-actions.position-justified,.form-actions.position-split{justify-content:space-between}.form-actions.orientation-vertical .desktop-actions{display:flex;flex-direction:column}.form-actions.orientation-horizontal .desktop-actions{display:flex;flex-direction:row}.form-actions.spacing-compact{gap:.25rem}.form-actions.spacing-normal{gap:.5rem}.form-actions.spacing-spacious{gap:1rem}.form-actions.spacing-compact .desktop-actions{gap:.25rem}.form-actions.spacing-normal .desktop-actions{gap:.5rem}.form-actions.spacing-spacious .desktop-actions{gap:1rem}.form-actions.loading{pointer-events:none;opacity:.7}.mobile-actions{display:none}.desktop-actions{display:flex;flex-wrap:wrap;gap:.5rem}.form-actions__hint{display:inline-flex;align-items:center;gap:.45rem;max-width:min(100%,32rem);padding:.5rem .65rem;border-radius:8px;font-size:.84rem;line-height:1.25;color:var(--md-sys-color-on-secondary-container);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-secondary-container) 82%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary-container) 44%,transparent));border:1px solid color-mix(in srgb,var(--md-sys-color-secondary) 22%,transparent)}.form-actions__hint-icon{flex:0 0 auto;font-size:18px;width:18px;height:18px;line-height:18px}.form-actions.position-split .desktop-actions{width:100%;justify-content:space-between;align-items:center}.desktop-actions-group{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.desktop-actions-group.primary-group{justify-content:flex-end}.desktop-actions-group.secondary-group{opacity:.94}.form-actions .mdc-button{letter-spacing:.01em;border-radius:999px}.form-actions.editorial-visual-context .mdc-button{border-radius:var(--editorial-button-radius, 999px);font-family:var(--editorial-body-font-family, inherit)}.form-actions .mdc-button.size-small{min-height:36px;padding-inline:12px;font-size:.82rem}.form-actions .mdc-button.size-medium{min-height:44px;padding-inline:18px}.form-actions .mdc-button.size-large{min-height:50px;padding-inline:24px;font-size:.98rem}.form-actions .mat-mdc-raised-button,.form-actions .mat-mdc-unelevated-button{box-shadow:none}.form-actions .mat-mdc-raised-button:not([disabled]),.form-actions .mat-mdc-unelevated-button:not([disabled]){--mat-button-protected-container-color: var(--md-sys-color-primary);--mat-button-protected-label-text-color: var(--md-sys-color-on-primary);--mat-button-filled-container-color: var(--md-sys-color-primary);--mat-button-filled-label-text-color: var(--md-sys-color-on-primary);--mdc-protected-button-container-color: var(--md-sys-color-primary);--mdc-protected-button-label-text-color: var(--md-sys-color-on-primary);--mdc-filled-button-container-color: var(--md-sys-color-primary);--mdc-filled-button-label-text-color: var(--md-sys-color-on-primary)}.form-actions .mat-mdc-raised-button[disabled],.form-actions .mat-mdc-unelevated-button[disabled]{opacity:.55}.form-actions .mat-mdc-outlined-button,.form-actions .mat-mdc-button-base.mat-mdc-outlined-button{background:transparent}.form-actions.editorial-visual-context .mat-mdc-raised-button,.form-actions.editorial-visual-context .mat-mdc-unelevated-button{--mat-button-protected-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-protected-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mat-button-filled-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-filled-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-protected-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-protected-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-filled-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-filled-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));box-shadow:var(--editorial-floating-shadow, var(--editorial-card-shadow, none))}.form-actions.editorial-visual-context .mat-mdc-outlined-button{--mdc-outlined-button-outline-color: var(--editorial-border-color, var(--md-sys-color-outline-variant));--mdc-outlined-button-label-text-color: var( --editorial-cta-secondary-text, var(--editorial-text-primary, var(--md-sys-color-on-surface)) );background:color-mix(in srgb,var(--editorial-cta-secondary, var(--editorial-surface-secondary, var(--md-sys-color-surface-container-low))) 72%,transparent)}.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-primary,.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-accent{--mdc-outlined-button-label-text-color: var(--editorial-cta-primary, var(--md-sys-color-primary));background:transparent}.form-actions button .mat-icon{font-size:20px;width:20px;height:20px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.form-actions button .mat-icon+span{margin-left:8px}@media(max-width:768px){.form-actions{padding:.9rem;border-radius:16px}:host([data-actions-placement=afterSections]) .form-actions{border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px)}.form-actions.mobile-menu-active .desktop-actions{display:none}.form-actions.mobile-menu-active .mobile-actions{display:flex;align-items:center;gap:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.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: i3.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i6.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: i6.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i6.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
2631
2634
  }
2632
2635
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFormActionsComponent, decorators: [{
2633
2636
  type: Component,
@@ -2639,7 +2642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
2639
2642
  MatMenuModule,
2640
2643
  PraxisIconButtonComponent,
2641
2644
  PraxisIconDirective,
2642
- ], providers: [providePraxisI18nConfig(PRAXIS_DYNAMIC_FORM_I18N_CONFIG)], template: "<div\n class=\"form-actions\"\n [ngClass]=\"viewClassMap\"\n [ngStyle]=\"actions?.containerStyles\"\n>\n @if (submitError) {\n <div class=\"alert alert--error\" role=\"alert\" aria-live=\"polite\">\n <mat-icon class=\"alert__icon\" aria-hidden=\"true\">error_outline</mat-icon>\n <div>\n <h4 class=\"alert__title\">Erro ao salvar</h4>\n <p class=\"alert__text\">{{ submitError }}</p>\n </div>\n </div>\n }\n @if (shouldShowInvalidSubmitHint()) {\n <div class=\"form-actions__hint\" role=\"status\" aria-live=\"polite\">\n <mat-icon class=\"form-actions__hint-icon\" aria-hidden=\"true\">info</mat-icon>\n <span>{{ getInvalidSubmitHint() }}</span>\n </div>\n }\n <div class=\"desktop-actions\">\n @if (isSplitLayout()) {\n <div class=\"desktop-actions-group secondary-group\">\n @for (button of getSecondaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n <div class=\"desktop-actions-group primary-group\">\n @for (button of getPrimaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n } @else {\n @for (button of getActionButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n }\n </div>\n @if (actions?.mobile?.collapseToMenu) {\n <div class=\"mobile-actions\">\n @for (button of getVisibleButtons(); track button.id) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n @if (getCollapsedButtons().length > 0) {\n <button\n praxisIconButton=\"more_vert\"\n size=\"compact\"\n [matMenuTriggerFor]=\"actionsMenu\"\n aria-label=\"More actions\"\n >\n </button>\n <mat-menu #actionsMenu=\"matMenu\">\n @for (button of getCollapsedButtons(); track button.id) {\n <button\n mat-menu-item\n (click)=\"onActionButtonClick(button, $event)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\"\n >\n @if (button.icon) { <mat-icon [praxisIcon]=\"button.icon\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.form-actions{display:flex;justify-content:flex-end;align-items:center;gap:.75rem;padding:1rem 1.1rem;z-index:1;font-family:inherit;border-radius:18px;background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%));border:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.with-divider{border-top:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}:host([data-actions-placement=afterSections]) .form-actions{margin:0;border-right:0;border-bottom:0;border-left:0;border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px);background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%))}:host([data-actions-placement=afterSections]) .form-actions.with-divider{border-top-color:var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.editorial-visual-context{font-family:var(--editorial-body-font-family, inherit)}.form-actions.editorial-visual-context.with-divider{border-top:var(--editorial-card-border-width, 1px) solid color-mix(in srgb,var(--editorial-border-color, var(--md-sys-color-outline-variant)) 72%,transparent)}.form-actions.sticky{position:sticky;bottom:0}.form-actions.position-left{justify-content:flex-start}.form-actions.position-center{justify-content:center}.form-actions.position-right{justify-content:flex-end}.form-actions.position-justified,.form-actions.position-split{justify-content:space-between}.form-actions.orientation-vertical .desktop-actions{display:flex;flex-direction:column}.form-actions.orientation-horizontal .desktop-actions{display:flex;flex-direction:row}.form-actions.spacing-compact{gap:.25rem}.form-actions.spacing-normal{gap:.5rem}.form-actions.spacing-spacious{gap:1rem}.form-actions.spacing-compact .desktop-actions{gap:.25rem}.form-actions.spacing-normal .desktop-actions{gap:.5rem}.form-actions.spacing-spacious .desktop-actions{gap:1rem}.form-actions.loading{pointer-events:none;opacity:.7}.mobile-actions{display:none}.desktop-actions{display:flex;flex-wrap:wrap;gap:.5rem}.form-actions__hint{display:inline-flex;align-items:center;gap:.45rem;max-width:min(100%,32rem);padding:.5rem .65rem;border-radius:8px;font-size:.84rem;line-height:1.25;color:var(--md-sys-color-on-secondary-container);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-secondary-container) 82%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary-container) 44%,transparent));border:1px solid color-mix(in srgb,var(--md-sys-color-secondary) 22%,transparent)}.form-actions__hint-icon{flex:0 0 auto;font-size:18px;width:18px;height:18px;line-height:18px}.form-actions.position-split .desktop-actions{width:100%;justify-content:space-between;align-items:center}.desktop-actions-group{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.desktop-actions-group.primary-group{justify-content:flex-end}.desktop-actions-group.secondary-group{opacity:.94}.form-actions .mdc-button{letter-spacing:.01em;border-radius:999px}.form-actions.editorial-visual-context .mdc-button{border-radius:var(--editorial-button-radius, 999px);font-family:var(--editorial-body-font-family, inherit)}.form-actions .mdc-button.size-small{min-height:36px;padding-inline:12px;font-size:.82rem}.form-actions .mdc-button.size-medium{min-height:44px;padding-inline:18px}.form-actions .mdc-button.size-large{min-height:50px;padding-inline:24px;font-size:.98rem}.form-actions .mat-mdc-raised-button,.form-actions .mat-mdc-unelevated-button{box-shadow:none}.form-actions .mat-mdc-raised-button:not([disabled]),.form-actions .mat-mdc-unelevated-button:not([disabled]){--mat-button-protected-container-color: var(--md-sys-color-primary);--mat-button-protected-label-text-color: var(--md-sys-color-on-primary);--mat-button-filled-container-color: var(--md-sys-color-primary);--mat-button-filled-label-text-color: var(--md-sys-color-on-primary);--mdc-protected-button-container-color: var(--md-sys-color-primary);--mdc-protected-button-label-text-color: var(--md-sys-color-on-primary);--mdc-filled-button-container-color: var(--md-sys-color-primary);--mdc-filled-button-label-text-color: var(--md-sys-color-on-primary)}.form-actions .mat-mdc-raised-button[disabled],.form-actions .mat-mdc-unelevated-button[disabled]{opacity:.55}.form-actions .mat-mdc-outlined-button,.form-actions .mat-mdc-button-base.mat-mdc-outlined-button{background:transparent}.form-actions.editorial-visual-context .mat-mdc-raised-button,.form-actions.editorial-visual-context .mat-mdc-unelevated-button{--mat-button-protected-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-protected-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mat-button-filled-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-filled-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-protected-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-protected-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-filled-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-filled-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));box-shadow:var(--editorial-floating-shadow, var(--editorial-card-shadow, none))}.form-actions.editorial-visual-context .mat-mdc-outlined-button{--mdc-outlined-button-outline-color: var(--editorial-border-color, var(--md-sys-color-outline-variant));--mdc-outlined-button-label-text-color: var( --editorial-cta-secondary-text, var(--editorial-text-primary, var(--md-sys-color-on-surface)) );background:color-mix(in srgb,var(--editorial-cta-secondary, var(--editorial-surface-secondary, var(--md-sys-color-surface-container-low))) 72%,transparent)}.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-primary,.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-accent{--mdc-outlined-button-label-text-color: var(--editorial-cta-primary, var(--md-sys-color-primary));background:transparent}.form-actions button .mat-icon{font-size:20px;width:20px;height:20px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.form-actions button .mat-icon+span{margin-left:8px}@media(max-width:768px){.form-actions{padding:.9rem;border-radius:16px}:host([data-actions-placement=afterSections]) .form-actions{border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px)}.form-actions.mobile-menu-active .desktop-actions{display:none}.form-actions.mobile-menu-active .mobile-actions{display:flex;align-items:center;gap:.5rem}}\n"] }]
2645
+ ], providers: [providePraxisI18nConfig(PRAXIS_DYNAMIC_FORM_I18N_CONFIG)], template: "<div\n class=\"form-actions\"\n [ngClass]=\"viewClassMap\"\n [ngStyle]=\"actions?.containerStyles\"\n>\n @if (submitError) {\n <div class=\"alert alert--error\" role=\"alert\" aria-live=\"polite\">\n <mat-icon class=\"alert__icon\" aria-hidden=\"true\">error_outline</mat-icon>\n <div>\n <h4 class=\"alert__title\">{{ submitErrorTitle }}</h4>\n <p class=\"alert__text\">{{ submitError }}</p>\n </div>\n </div>\n }\n @if (shouldShowInvalidSubmitHint()) {\n <div class=\"form-actions__hint\" role=\"status\" aria-live=\"polite\">\n <mat-icon class=\"form-actions__hint-icon\" aria-hidden=\"true\">info</mat-icon>\n <span>{{ getInvalidSubmitHint() }}</span>\n </div>\n }\n <div class=\"desktop-actions\">\n @if (isSplitLayout()) {\n <div class=\"desktop-actions-group secondary-group\">\n @for (button of getSecondaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n <div class=\"desktop-actions-group primary-group\">\n @for (button of getPrimaryButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n </div>\n } @else {\n @for (button of getActionButtons(); track button.id) {\n @if (button.visible) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button\n [type]=\"button.type || 'button'\"\n [color]=\"getMatColor(button)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [attr.aria-busy]=\"button.loading ? 'true' : null\"\n [matTooltip]=\"button.tooltip\"\n (click)=\"onActionButtonClick(button, $event)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n }\n }\n </div>\n @if (actions?.mobile?.collapseToMenu) {\n <div class=\"mobile-actions\">\n @for (button of getVisibleButtons(); track button.id) {\n @switch (button.variant || 'raised') {\n @case ('stroked') {\n <button mat-stroked-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('flat') {\n <button mat-flat-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon [praxisIcon]=\"button.icon\" aria-hidden=\"true\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @case ('fab') {\n <button mat-fab [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n @default {\n <button mat-raised-button [type]=\"button.type || 'button'\" [color]=\"getMatColor(button)\" [disabled]=\"isActionButtonDisabled(button)\" [attr.aria-busy]=\"button.loading ? 'true' : null\" [matTooltip]=\"button.tooltip\" (click)=\"onActionButtonClick(button, $event)\" [attr.aria-label]=\"button.label\" [ngClass]=\"getButtonNgClass(button)\" [ngStyle]=\"getButtonStyles(button)\">\n @if (button.loading) { <mat-icon class=\"loading-icon\" aria-hidden=\"true\">autorenew</mat-icon> }\n @if (button.icon && !button.loading) { <mat-icon aria-hidden=\"true\">{{ button.icon }}</mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n }\n }\n @if (getCollapsedButtons().length > 0) {\n <button\n praxisIconButton=\"more_vert\"\n size=\"compact\"\n [matMenuTriggerFor]=\"actionsMenu\"\n aria-label=\"More actions\"\n >\n </button>\n <mat-menu #actionsMenu=\"matMenu\">\n @for (button of getCollapsedButtons(); track button.id) {\n <button\n mat-menu-item\n (click)=\"onActionButtonClick(button, $event)\"\n [disabled]=\"isActionButtonDisabled(button)\"\n [ngClass]=\"getButtonNgClass(button)\"\n [ngStyle]=\"getButtonStyles(button)\"\n >\n @if (button.icon) { <mat-icon [praxisIcon]=\"button.icon\"></mat-icon> }\n <span>{{ button.label }}</span>\n </button>\n }\n </mat-menu>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.form-actions{display:flex;justify-content:flex-end;align-items:center;gap:.75rem;padding:1rem 1.1rem;z-index:1;font-family:inherit;border-radius:18px;background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%));border:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.with-divider{border-top:1px solid var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}:host([data-actions-placement=afterSections]) .form-actions{margin:0;border-right:0;border-bottom:0;border-left:0;border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px);background:var(--pfx-form-footer-surface, color-mix(in srgb, var(--md-sys-color-surface-container) 78%, var(--md-sys-color-surface-container-low) 22%))}:host([data-actions-placement=afterSections]) .form-actions.with-divider{border-top-color:var(--pfx-form-footer-border, color-mix(in srgb, var(--md-sys-color-outline-variant) 76%, transparent))}.form-actions.editorial-visual-context{font-family:var(--editorial-body-font-family, inherit)}.form-actions.editorial-visual-context.with-divider{border-top:var(--editorial-card-border-width, 1px) solid color-mix(in srgb,var(--editorial-border-color, var(--md-sys-color-outline-variant)) 72%,transparent)}.form-actions.sticky{position:sticky;bottom:0}.form-actions.position-left{justify-content:flex-start}.form-actions.position-center{justify-content:center}.form-actions.position-right{justify-content:flex-end}.form-actions.position-justified,.form-actions.position-split{justify-content:space-between}.form-actions.orientation-vertical .desktop-actions{display:flex;flex-direction:column}.form-actions.orientation-horizontal .desktop-actions{display:flex;flex-direction:row}.form-actions.spacing-compact{gap:.25rem}.form-actions.spacing-normal{gap:.5rem}.form-actions.spacing-spacious{gap:1rem}.form-actions.spacing-compact .desktop-actions{gap:.25rem}.form-actions.spacing-normal .desktop-actions{gap:.5rem}.form-actions.spacing-spacious .desktop-actions{gap:1rem}.form-actions.loading{pointer-events:none;opacity:.7}.mobile-actions{display:none}.desktop-actions{display:flex;flex-wrap:wrap;gap:.5rem}.form-actions__hint{display:inline-flex;align-items:center;gap:.45rem;max-width:min(100%,32rem);padding:.5rem .65rem;border-radius:8px;font-size:.84rem;line-height:1.25;color:var(--md-sys-color-on-secondary-container);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-secondary-container) 82%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary-container) 44%,transparent));border:1px solid color-mix(in srgb,var(--md-sys-color-secondary) 22%,transparent)}.form-actions__hint-icon{flex:0 0 auto;font-size:18px;width:18px;height:18px;line-height:18px}.form-actions.position-split .desktop-actions{width:100%;justify-content:space-between;align-items:center}.desktop-actions-group{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.desktop-actions-group.primary-group{justify-content:flex-end}.desktop-actions-group.secondary-group{opacity:.94}.form-actions .mdc-button{letter-spacing:.01em;border-radius:999px}.form-actions.editorial-visual-context .mdc-button{border-radius:var(--editorial-button-radius, 999px);font-family:var(--editorial-body-font-family, inherit)}.form-actions .mdc-button.size-small{min-height:36px;padding-inline:12px;font-size:.82rem}.form-actions .mdc-button.size-medium{min-height:44px;padding-inline:18px}.form-actions .mdc-button.size-large{min-height:50px;padding-inline:24px;font-size:.98rem}.form-actions .mat-mdc-raised-button,.form-actions .mat-mdc-unelevated-button{box-shadow:none}.form-actions .mat-mdc-raised-button:not([disabled]),.form-actions .mat-mdc-unelevated-button:not([disabled]){--mat-button-protected-container-color: var(--md-sys-color-primary);--mat-button-protected-label-text-color: var(--md-sys-color-on-primary);--mat-button-filled-container-color: var(--md-sys-color-primary);--mat-button-filled-label-text-color: var(--md-sys-color-on-primary);--mdc-protected-button-container-color: var(--md-sys-color-primary);--mdc-protected-button-label-text-color: var(--md-sys-color-on-primary);--mdc-filled-button-container-color: var(--md-sys-color-primary);--mdc-filled-button-label-text-color: var(--md-sys-color-on-primary)}.form-actions .mat-mdc-raised-button[disabled],.form-actions .mat-mdc-unelevated-button[disabled]{opacity:.55}.form-actions .mat-mdc-outlined-button,.form-actions .mat-mdc-button-base.mat-mdc-outlined-button{background:transparent}.form-actions.editorial-visual-context .mat-mdc-raised-button,.form-actions.editorial-visual-context .mat-mdc-unelevated-button{--mat-button-protected-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-protected-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mat-button-filled-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mat-button-filled-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-protected-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-protected-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));--mdc-filled-button-container-color: var(--editorial-cta-primary, var(--md-sys-color-primary));--mdc-filled-button-label-text-color: var(--editorial-cta-primary-text, var(--md-sys-color-on-primary));box-shadow:var(--editorial-floating-shadow, var(--editorial-card-shadow, none))}.form-actions.editorial-visual-context .mat-mdc-outlined-button{--mdc-outlined-button-outline-color: var(--editorial-border-color, var(--md-sys-color-outline-variant));--mdc-outlined-button-label-text-color: var( --editorial-cta-secondary-text, var(--editorial-text-primary, var(--md-sys-color-on-surface)) );background:color-mix(in srgb,var(--editorial-cta-secondary, var(--editorial-surface-secondary, var(--md-sys-color-surface-container-low))) 72%,transparent)}.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-primary,.form-actions.editorial-visual-context .mat-mdc-outlined-button.tone-accent{--mdc-outlined-button-label-text-color: var(--editorial-cta-primary, var(--md-sys-color-primary));background:transparent}.form-actions button .mat-icon{font-size:20px;width:20px;height:20px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.form-actions button .mat-icon+span{margin-left:8px}@media(max-width:768px){.form-actions{padding:.9rem;border-radius:16px}:host([data-actions-placement=afterSections]) .form-actions{border-radius:0 0 var(--pfx-editorial-form-radius, 8px) var(--pfx-editorial-form-radius, 8px)}.form-actions.mobile-menu-active .desktop-actions{display:none}.form-actions.mobile-menu-active .mobile-actions{display:flex;align-items:center;gap:.5rem}}\n"] }]
2643
2646
  }], ctorParameters: () => [{ type: i1$1.KeyboardShortcutService }], propDecorators: { actions: [{
2644
2647
  type: Input
2645
2648
  }], editorialVisualContext: [{
@@ -11547,6 +11550,8 @@ class PraxisDynamicForm {
11547
11550
  destroy$ = new Subject();
11548
11551
  isDestroyed = false;
11549
11552
  formValueChangesSubscription = null;
11553
+ submitFieldErrorSubscriptions = new Map();
11554
+ activeSubmitErrorDetails = [];
11550
11555
  reactiveDeterminationSubscription = null;
11551
11556
  reactiveDeterminationScheduledBuildId = 0;
11552
11557
  dependencyPolicySubscription = null;
@@ -13189,6 +13194,7 @@ class PraxisDynamicForm {
13189
13194
  this.cdr.markForCheck();
13190
13195
  }
13191
13196
  buildFormFromConfig(options = { reason: 'initialization' }) {
13197
+ this.resetSubmitErrorState();
13192
13198
  this.runtimeFieldMetadata = null;
13193
13199
  const fieldMetadata = this.getRuntimeFieldMetadata();
13194
13200
  const previousState = options.preserveRuntimeState
@@ -15580,9 +15586,9 @@ class PraxisDynamicForm {
15580
15586
  }
15581
15587
  }
15582
15588
  async prepareAndSubmit() {
15589
+ this.clearSubmitFieldErrors();
15583
15590
  if (!(await this.waitForReactiveDeterminationsBeforeSubmit()))
15584
15591
  return;
15585
- this.clearSubmitFieldErrors();
15586
15592
  // Hooks: beforeValidate
15587
15593
  try {
15588
15594
  await this.runHooks('beforeValidate');
@@ -15698,6 +15704,7 @@ class PraxisDynamicForm {
15698
15704
  }))
15699
15705
  .subscribe({
15700
15706
  next: (result) => {
15707
+ this.clearSubmitFieldErrors();
15701
15708
  // Encerrar loading
15702
15709
  try {
15703
15710
  this._activeSnack?.dismiss();
@@ -15766,18 +15773,11 @@ class PraxisDynamicForm {
15766
15773
  // Mensagens de erro configuráveis
15767
15774
  try {
15768
15775
  const normalizedError = this.errorMessages.normalizeSubmitError(error);
15776
+ this.activeSubmitErrorDetails = normalizedError.details.map((detail) => ({
15777
+ ...detail,
15778
+ }));
15769
15779
  this.applySubmitFieldErrors(normalizedError.details);
15770
- const msgs = this.config.messages;
15771
- let errorMsg = msgs?.customActions?.submit?.error;
15772
- if (!errorMsg) {
15773
- errorMsg =
15774
- operation === 'create'
15775
- ? msgs?.createRegistryError
15776
- : msgs?.updateRegistryError;
15777
- }
15778
- if (!errorMsg) {
15779
- errorMsg = normalizedError.message;
15780
- }
15780
+ const errorMsg = this.resolveSubmitErrorMessage(normalizedError, operation);
15781
15781
  this.submitError = errorMsg;
15782
15782
  this.snackBar.open(errorMsg, undefined, {
15783
15783
  duration: 4000,
@@ -15810,23 +15810,32 @@ class PraxisDynamicForm {
15810
15810
  const control = this.form.get(target);
15811
15811
  if (!control)
15812
15812
  continue;
15813
- if (control.errors?.['server'])
15813
+ if (control.errors?.['server'] || this.submitFieldErrorSubscriptions.has(control)) {
15814
15814
  continue;
15815
+ }
15815
15816
  control.setErrors({
15816
15817
  ...(control.errors || {}),
15817
15818
  server: { code: detail.code, message: detail.message },
15818
15819
  });
15819
15820
  control.markAsTouched();
15821
+ const subscription = control.valueChanges
15822
+ .pipe(take(1), takeUntil(this.destroy$))
15823
+ .subscribe(() => {
15824
+ this.submitFieldErrorSubscriptions.delete(control);
15825
+ this.clearSubmitFieldError(control);
15826
+ this.activeSubmitErrorDetails = this.activeSubmitErrorDetails.filter((activeDetail) => this.normalizeSubmitErrorTarget(activeDetail.target) !== target);
15827
+ this.submitError = this.activeSubmitErrorDetails[0]?.message ?? null;
15828
+ this.cdr.markForCheck();
15829
+ });
15830
+ this.submitFieldErrorSubscriptions.set(control, subscription);
15820
15831
  }
15821
15832
  }
15822
15833
  clearSubmitFieldErrors() {
15834
+ this.resetSubmitErrorState();
15835
+ if (!this.form)
15836
+ return;
15823
15837
  const visit = (control) => {
15824
- const currentErrors = control.errors;
15825
- if (currentErrors?.['server']) {
15826
- const nextErrors = { ...currentErrors };
15827
- delete nextErrors['server'];
15828
- control.setErrors(Object.keys(nextErrors).length ? nextErrors : null);
15829
- }
15838
+ this.clearSubmitFieldError(control);
15830
15839
  const children = control.controls;
15831
15840
  if (Array.isArray(children)) {
15832
15841
  children.forEach(visit);
@@ -15837,6 +15846,33 @@ class PraxisDynamicForm {
15837
15846
  };
15838
15847
  visit(this.form);
15839
15848
  }
15849
+ clearSubmitFieldError(control) {
15850
+ const currentErrors = control.errors;
15851
+ if (!currentErrors?.['server'])
15852
+ return;
15853
+ const nextErrors = { ...currentErrors };
15854
+ delete nextErrors['server'];
15855
+ control.setErrors(Object.keys(nextErrors).length ? nextErrors : null);
15856
+ }
15857
+ resetSubmitErrorState() {
15858
+ for (const subscription of this.submitFieldErrorSubscriptions.values()) {
15859
+ subscription.unsubscribe();
15860
+ }
15861
+ this.submitFieldErrorSubscriptions.clear();
15862
+ this.activeSubmitErrorDetails = [];
15863
+ this.submitError = null;
15864
+ }
15865
+ resolveSubmitErrorMessage(normalizedError, operation) {
15866
+ if (normalizedError.details.length) {
15867
+ return normalizedError.message;
15868
+ }
15869
+ const messages = this.config.messages;
15870
+ return messages?.customActions?.submit?.error
15871
+ || (operation === 'create'
15872
+ ? messages?.createRegistryError
15873
+ : messages?.updateRegistryError)
15874
+ || normalizedError.message;
15875
+ }
15840
15876
  normalizeSubmitErrorTarget(target) {
15841
15877
  const normalized = String(target || '').trim();
15842
15878
  if (!normalized)
@@ -19464,6 +19500,7 @@ class PraxisDynamicForm {
19464
19500
  }
19465
19501
  ngOnDestroy() {
19466
19502
  this.isDestroyed = true;
19503
+ this.resetSubmitErrorState();
19467
19504
  this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot());
19468
19505
  this.aiAssistantStateSubscription?.unsubscribe();
19469
19506
  this.aiAssistantStateSubscription = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/dynamic-form",
3
- "version": "9.0.5-rc.29",
3
+ "version": "9.0.5-rc.30",
4
4
  "description": "Angular dynamic form engine for Praxis UI: metadata-driven forms, hooks, and services integrating @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
@@ -9,13 +9,13 @@
9
9
  "@angular/forms": "^21.0.0",
10
10
  "@angular/material": "^21.0.0",
11
11
  "@angular/router": "^21.0.0",
12
- "@praxisui/ai": "^9.0.5-rc.29",
13
- "@praxisui/dynamic-fields": "^9.0.5-rc.29",
14
- "@praxisui/metadata-editor": "^9.0.5-rc.29",
15
- "@praxisui/rich-content": "^9.0.5-rc.29",
16
- "@praxisui/settings-panel": "^9.0.5-rc.29",
17
- "@praxisui/visual-builder": "^9.0.5-rc.29",
18
- "@praxisui/core": "^9.0.5-rc.29",
12
+ "@praxisui/ai": "^9.0.5-rc.30",
13
+ "@praxisui/dynamic-fields": "^9.0.5-rc.30",
14
+ "@praxisui/metadata-editor": "^9.0.5-rc.30",
15
+ "@praxisui/rich-content": "^9.0.5-rc.30",
16
+ "@praxisui/settings-panel": "^9.0.5-rc.30",
17
+ "@praxisui/visual-builder": "^9.0.5-rc.30",
18
+ "@praxisui/core": "^9.0.5-rc.30",
19
19
  "rxjs": "^7.8.0"
20
20
  },
21
21
  "dependencies": {
@@ -502,6 +502,19 @@ config local persistida -> config default criada do schema remoto -> erro de ini
502
502
 
503
503
  Falhas de inicialização (`config-load`/`form-build`) são bloqueantes e emitem `initializationError`. Divergências de schema são tratadas como estado (`schemaOutdated`) com emissão de evento, sem interromper formulário já carregado.
504
504
 
505
+ No submit, `RestApiResponse.errors` é materializado somente quando a resposta possui status HTTP
506
+ `4xx` conhecido. Cada detalhe cujo `target` resolve um controle é anexado como erro `server`, sem
507
+ remover validadores do cliente, e desaparece quando aquele controle muda. Erros de outros campos
508
+ permanecem ativos; todos os erros de servidor são limpos no início da próxima tentativa e após
509
+ sucesso. `target` aceita path Angular pontuado ou JSON Pointer. Targets desconhecidos não são
510
+ inferidos por texto e continuam como feedback global.
511
+
512
+ Precedência do feedback: resposta `4xx` com detalhes governados > mensagem customizada de submit
513
+ ou create/update > fallback normalizado e localizado. Falhas de rede, respostas `5xx` e respostas
514
+ sem status confiável nunca expõem `message`/`errors` do payload e usam as chaves
515
+ `global.submitError.*`. `visibleWhen`, mensagens locais e validação visual não substituem a
516
+ autorização e a validação de negócio do backend.
517
+
505
518
  ### Fail-open / fail-closed behavior
506
519
 
507
520
  | Condition | Mode | Runtime behavior | Consumer impact |
@@ -444,6 +444,7 @@ declare class PraxisFormActionsComponent implements OnChanges, OnDestroy {
444
444
  private invalidSubmitHintCacheSignature;
445
445
  private invalidSubmitHintCacheValue;
446
446
  private readonly invalidSubmitHint;
447
+ readonly submitErrorTitle: string;
447
448
  /** Configuration object describing form action buttons */
448
449
  actions?: FormConfig['actions'];
449
450
  /** Whether the actions are rendered inside an editorial visual context */
@@ -938,6 +939,8 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
938
939
  private destroy$;
939
940
  private isDestroyed;
940
941
  private formValueChangesSubscription;
942
+ private readonly submitFieldErrorSubscriptions;
943
+ private activeSubmitErrorDetails;
941
944
  private reactiveDeterminationSubscription;
942
945
  private reactiveDeterminationScheduledBuildId;
943
946
  private dependencyPolicySubscription;
@@ -1261,6 +1264,9 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
1261
1264
  private waitForReactiveDeterminationsBeforeSubmit;
1262
1265
  private applySubmitFieldErrors;
1263
1266
  private clearSubmitFieldErrors;
1267
+ private clearSubmitFieldError;
1268
+ private resetSubmitErrorState;
1269
+ private resolveSubmitErrorMessage;
1264
1270
  private normalizeSubmitErrorTarget;
1265
1271
  private navigateAfterSave;
1266
1272
  private shouldConfirmCancelOnDirty;