@praxisui/crud 9.0.4-rc.2 → 9.0.4-rc.20

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
@@ -20,7 +20,7 @@ Use this package to combine `@praxisui/table`, `@praxisui/dynamic-form`, governe
20
20
  ## Install
21
21
 
22
22
  ```bash
23
- npm i @praxisui/crud@latest
23
+ npm i @praxisui/crud@rc
24
24
  ```
25
25
 
26
26
  Peer dependencies:
@@ -123,6 +123,21 @@ Child component semantics stay with their owners:
123
123
  - dialog presentation belongs to `@praxisui/dialog` and host adapters
124
124
  - page composition belongs to `@praxisui/page-builder`
125
125
 
126
+ Item surfaces published by capabilities are not limited to the conventional
127
+ `create`, `view`, and `edit` ids. A row may expose domain surfaces such as
128
+ `payroll-history`, `address`, or `equipment-custody`. The runtime trusts the
129
+ canonical `ResourceSurfaceCatalogItem` carried by the row action; if that object
130
+ is not forwarded, it resolves the exact `surface.id` through the row HATEOAS
131
+ links. A surface is distinguished from a business action by its `kind` and is
132
+ materialized with the corresponding widget. Related collection surfaces open a
133
+ governed `praxis-table` filtered by the selected parent id, while unavailable
134
+ surfaces remain disabled according to their published availability decision.
135
+
136
+ Consumers should not translate these domain surfaces into local CRUD aliases or
137
+ duplicate their filters in the host. The backend owns relationship metadata and
138
+ availability; `@praxisui/core`, `@praxisui/table`, and `@praxisui/crud` own the
139
+ materialization and immediate interaction feedback.
140
+
126
141
  ## Open Modes
127
142
 
128
143
  CRUD actions can open by route, modal, or drawer. `CrudLauncherService` resolves the final mode from persisted overrides, action metadata, and global/default configuration.
@@ -165,6 +180,10 @@ maximizing or restoring. The default edge is right/top; a configured left edge
165
180
  is preserved by the same lifecycle. `defaults.modal.edgeGap` controls the
166
181
  viewport inset while maximized. Provide `CRUD_DRAWER_ADAPTER` only when a
167
182
  host must replace that standard shell with a real corporate drawer implementation.
183
+ Upstream surface materializers should publish the semantic `openMode` and leave
184
+ geometry absent unless the host intentionally overrides it with a valid
185
+ `DialogPosition` object. Values such as `position: "end"` are not drawer
186
+ positions and must not be used to imitate a side panel.
168
187
 
169
188
  For edit drawers and modals, the runtime presents the selected record identity
170
189
  above the command form when the table has materialized it. An explicit valid
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-31T10:13:34.390Z",
3
+ "generatedAt": "2026-08-04T11:45:12.207Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "9.0.4-rc.2",
5
+ "packageVersion": "9.0.4-rc.20",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -11403,6 +11403,24 @@
11403
11403
  ],
11404
11404
  "description": "Metodo HTTP canonico para o submit quando o backend publica operacao explicita."
11405
11405
  },
11406
+ {
11407
+ "path": "inputs.submitIdempotencyKey",
11408
+ "category": "inputs",
11409
+ "valueKind": "string",
11410
+ "description": "Identidade estavel da tentativa de comando, materializada a partir da politica execution da action."
11411
+ },
11412
+ {
11413
+ "path": "inputs.submitCorrelationId",
11414
+ "category": "inputs",
11415
+ "valueKind": "string",
11416
+ "description": "Identificador de correlacao governado para rastreabilidade ponta a ponta do comando."
11417
+ },
11418
+ {
11419
+ "path": "inputs.submitResourceVersion",
11420
+ "category": "inputs",
11421
+ "valueKind": "string",
11422
+ "description": "Versao persistida esperada, transportada como If-Match quando exigida pela action."
11423
+ },
11406
11424
  {
11407
11425
  "path": "inputs.responseSchemaUrl",
11408
11426
  "category": "inputs",
@@ -11452,6 +11470,17 @@
11452
11470
  "description": "Preset usado somente na criacao inicial de FormConfig a partir de schema/metadata.",
11453
11471
  "safetyNotes": "Nao reprocessa layouts ja persistidos ou authorados."
11454
11472
  },
11473
+ {
11474
+ "path": "inputs.configPersistenceStrategy",
11475
+ "category": "inputs",
11476
+ "valueKind": "enum",
11477
+ "allowedValues": [
11478
+ "local-first",
11479
+ "input-first"
11480
+ ],
11481
+ "description": "Define se preferências locais persistidas ou o contrato explícito do host governam a materialização corrente.",
11482
+ "safetyNotes": "Use input-first em commands e surfaces transitórias; isso impede que mode e estados operacionais antigos sobrescrevam o schema atual."
11483
+ },
11455
11484
  {
11456
11485
  "path": "inputs.layoutPolicy",
11457
11486
  "category": "inputs",
@@ -11522,6 +11551,13 @@
11522
11551
  "description": "Define como uma linha groupedCommand com um unico campo usa o espaco restante. O default medium-and-wide expande spans 6 ou 8 e preserva campos compactos.",
11523
11552
  "safetyNotes": "Use preserve quando o espaco vazio for intencional; use all apenas quando ate campos xs/sm devam ocupar a linha completa."
11524
11553
  },
11554
+ {
11555
+ "path": "inputs.layoutPolicy.groupedCommand.contextFields",
11556
+ "category": "inputs",
11557
+ "valueKind": "array",
11558
+ "description": "Campos canônicos do request cujo valor vem do contexto da surface. Eles permanecem no FormGroup e no payload, sem renderizar controles redundantes.",
11559
+ "safetyNotes": "Use somente para campos realmente hidratados por initialValue ou leitura da entidade; nunca use para esconder dados que a pessoa ainda precisa informar."
11560
+ },
11525
11561
  {
11526
11562
  "path": "inputs.layoutPolicy.detailSummary",
11527
11563
  "category": "inputs",
@@ -12183,9 +12219,9 @@
12183
12219
  {
12184
12220
  "chunkIndex": 10,
12185
12221
  "chunkKind": "capabilities",
12186
- "content": "Schema Capability:\n - Path: fieldMetadata[].transformDisplayValue\n Category: transform\n Kind: expression\n Description: Transforma valor para exibição.\n\nSchema Capability:\n - Path: fieldMetadata[].transformSaveValue\n Category: transform\n Kind: expression\n Description: Transforma valor para salvar.\n\nSchema Capability:\n - Path: fieldMetadata[].textTransformApply\n Category: transform\n Kind: enum\n Description: Quando aplicar transformação de texto.\n\nSchema Capability:\n - Path: fieldMetadata[].inlineEditing\n Category: behavior\n Kind: boolean\n Description: Permite edição inline (tabelas).\n\nSchema Capability:\n - Path: fieldMetadata[].unique\n Category: validation\n Kind: boolean\n Description: Valida unicidade no backend.\n\nSchema Capability:\n - Path: fieldMetadata[].readOnly\n Category: behavior\n Kind: boolean\n Description: Campo somente leitura.\n\nSchema Capability:\n - Path: fieldMetadata[].disabled\n Category: behavior\n Kind: boolean\n Description: Campo desabilitado.\n\nSchema Capability:\n - Path: fieldMetadata[].defaultValue\n Category: data\n Kind: object\n Description: Valor inicial do campo.\n\nSchema Capability:\n - Path: fieldMetadata[].ariaLabel\n Category: accessibility\n Kind: string\n Description: Label para leitores de tela.\n\nSchema Capability:\n - Path: fieldMetadata[].ariaDescribedBy\n Category: accessibility\n Kind: string\n Description: IDs de descrição acessível.\n\nSchema Capability:\n - Path: fieldMetadata[].tabIndex\n Category: accessibility\n Kind: number\n Description: Ordem de tabulacao para teclado.\n\nSchema Capability:\n - Path: fieldMetadata[].accessKey\n Category: accessibility\n Kind: string\n Description: Atalho de teclado (accessKey).\n\nComponent Capability:\n - Path: inputs.resourcePath\n Category: inputs\n Kind: string\n Description: Caminho do recurso para carregar/salvar dados via CRUD.\n\nComponent Capability:\n - Path: inputs.resourceId\n Category: inputs\n Kind: string\n Description: ID do registro atual (string ou number).\n\nComponent Capability:\n - Path: inputs.initialValue\n Category: inputs\n Kind: object\n Description: Valores iniciais locais para campos host-authored ou complementares.\n\nComponent Capability:\n - Path: inputs.mode\n Category: inputs\n Kind: enum\n Description: Modo do formulario (create/edit/view).\n\nComponent Capability:\n - Path: inputs.config\n Category: inputs\n Kind: object\n Description: Configuracao completa do formulario (FormConfig).\n\nComponent Capability:\n - Path: inputs.schemaSource\n Category: inputs\n Kind: enum\n Description: Fonte de schema para construcao automatica (resource/filter).\n\nComponent Capability:\n - Path: inputs.schemaUrl\n Category: inputs\n Kind: string\n Description: Schema canonico explicito publicado pelo backend para hosts que seguem surfaces/actions/capabilities.\n\nComponent Capability:\n - Path: inputs.readUrl\n Category: inputs\n Kind: string\n Description: Endpoint canonico de leitura publicado por surface VIEW/READ_PROJECTION para hidratar a entidade exibida.\n\nComponent Capability:\n - Path: inputs.apiEndpointKey\n Category: inputs\n Kind: string\n Description: Endpoint canonico usado para resolver schemaUrl, readUrl e submitUrl relativos em hosts destacados.\n\nComponent Capability:\n - Path: inputs.submitUrl\n Category: inputs\n Kind: string\n Description: Endpoint canonico de submit publicado por surface/action.\n\nComponent Capability:\n - Path: inputs.submitMethod\n Category: inputs\n Kind: enum\n Description: Metodo HTTP canonico para o submit quando o backend publica operacao explicita.\n\nComponent Capability:\n - Path: inputs.responseSchemaUrl\n Category: inputs\n Kind: string\n Description: Schema de resposta publicado pelo backend para inspeção diagnostica/read-only do host.\n\nComponent Capability:\n - Path: inputs.enableCustomization\n Category: inputs\n Kind: boolean\n Description: Habilita a edicao de layout/configuracao no editor.\n\nComponent Capability:\n - Path: inputs.showAiAssistant\n Category: inputs\n Kind: boolean\n Description: Mostra os gatilhos do copiloto semantico Praxis para a instancia do formulario.\n\nComponent Capability:\n - Path: inputs.formId\n Category: inputs\n Kind: string\n Description: ID persistente para salvar configuracoes e preferencias.\n\nComponent Capability:\n - Path: inputs.editorialContext\n Category: inputs\n Kind: object\n Description: Override de contexto editorial fornecido pelo host para os blocos hospedados antes/depois do formulario.\n\nComponent Capability:\n - Path: inputs.layout\n Category: inputs\n Kind: object\n Description: Layout legado (FormLayout). Preferir inputs.config + sections.\n\nComponent Capability:\n - Path: inputs.generatedLayoutPreset\n Category: inputs\n Kind: enum\n Description: Preset usado somente na criacao inicial de FormConfig a partir de schema/metadata.\n\nComponent Capability:\n - Path: inputs.layoutPolicy\n Category: inputs\n Kind: object\n Description: Politica opt-in para materializar layout diretamente do schema atual sem depender de FormConfig.sections authoradas.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.source\n Category: inputs\n Kind: enum\n Description: Define se o layout vem de configuracao authorada ou do schema.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.preset\n Category: inputs\n Kind: enum\n Description: Preset de layout schema-driven. compactPresentation cobre detalhe read-only; groupedCommand cobre create/edit.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.intent\n Category: inputs\n Kind: enum\n Description: Intencao da surface: detalhe/apresentacao ou comando/editavel.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.lifecycle\n Category: inputs\n Kind: enum\n Description: Ciclo de materializacao do layout schema-driven.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.persistence\n Category: inputs\n Kind: enum\n Description: Define se sections geradas sao transient ou podem virar layout authorado apos detach explicito.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.groupedCommand.orphanFieldExpansion\n Category: inputs\n Kind: enum\n Description: Define como uma linha groupedCommand com um unico campo usa o espaco restante. O default medium-and-wide expande spans 6 ou 8 e preserva campos compactos.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary\n Category: inputs\n Kind: object\n Description: Politica declarativa para resumo read-only compacto em compactPresentation, sem FormConfig.sections locais.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeFields\n Category: inputs\n Kind: array\n Description: Lista de campos canônicos do schema que devem entrar no resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeFields\n Category: inputs\n Kind: array\n Description: Lista de campos canônicos do schema que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeGroups\n Category: inputs\n Kind: array\n Description: Lista de grupos do schema que devem entrar no resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeGroups\n Category: inputs\n Kind: array\n Description: Lista de grupos do schema que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeRoles\n Category: inputs\n Kind: array\n Description: Lista de papeis semanticos de campo que devem entrar no resumo read-only compacto.",
12222
+ "content": "Schema Capability:\n - Path: fieldMetadata[].transformDisplayValue\n Category: transform\n Kind: expression\n Description: Transforma valor para exibição.\n\nSchema Capability:\n - Path: fieldMetadata[].transformSaveValue\n Category: transform\n Kind: expression\n Description: Transforma valor para salvar.\n\nSchema Capability:\n - Path: fieldMetadata[].textTransformApply\n Category: transform\n Kind: enum\n Description: Quando aplicar transformação de texto.\n\nSchema Capability:\n - Path: fieldMetadata[].inlineEditing\n Category: behavior\n Kind: boolean\n Description: Permite edição inline (tabelas).\n\nSchema Capability:\n - Path: fieldMetadata[].unique\n Category: validation\n Kind: boolean\n Description: Valida unicidade no backend.\n\nSchema Capability:\n - Path: fieldMetadata[].readOnly\n Category: behavior\n Kind: boolean\n Description: Campo somente leitura.\n\nSchema Capability:\n - Path: fieldMetadata[].disabled\n Category: behavior\n Kind: boolean\n Description: Campo desabilitado.\n\nSchema Capability:\n - Path: fieldMetadata[].defaultValue\n Category: data\n Kind: object\n Description: Valor inicial do campo.\n\nSchema Capability:\n - Path: fieldMetadata[].ariaLabel\n Category: accessibility\n Kind: string\n Description: Label para leitores de tela.\n\nSchema Capability:\n - Path: fieldMetadata[].ariaDescribedBy\n Category: accessibility\n Kind: string\n Description: IDs de descrição acessível.\n\nSchema Capability:\n - Path: fieldMetadata[].tabIndex\n Category: accessibility\n Kind: number\n Description: Ordem de tabulacao para teclado.\n\nSchema Capability:\n - Path: fieldMetadata[].accessKey\n Category: accessibility\n Kind: string\n Description: Atalho de teclado (accessKey).\n\nComponent Capability:\n - Path: inputs.resourcePath\n Category: inputs\n Kind: string\n Description: Caminho do recurso para carregar/salvar dados via CRUD.\n\nComponent Capability:\n - Path: inputs.resourceId\n Category: inputs\n Kind: string\n Description: ID do registro atual (string ou number).\n\nComponent Capability:\n - Path: inputs.initialValue\n Category: inputs\n Kind: object\n Description: Valores iniciais locais para campos host-authored ou complementares.\n\nComponent Capability:\n - Path: inputs.mode\n Category: inputs\n Kind: enum\n Description: Modo do formulario (create/edit/view).\n\nComponent Capability:\n - Path: inputs.config\n Category: inputs\n Kind: object\n Description: Configuracao completa do formulario (FormConfig).\n\nComponent Capability:\n - Path: inputs.schemaSource\n Category: inputs\n Kind: enum\n Description: Fonte de schema para construcao automatica (resource/filter).\n\nComponent Capability:\n - Path: inputs.schemaUrl\n Category: inputs\n Kind: string\n Description: Schema canonico explicito publicado pelo backend para hosts que seguem surfaces/actions/capabilities.\n\nComponent Capability:\n - Path: inputs.readUrl\n Category: inputs\n Kind: string\n Description: Endpoint canonico de leitura publicado por surface VIEW/READ_PROJECTION para hidratar a entidade exibida.\n\nComponent Capability:\n - Path: inputs.apiEndpointKey\n Category: inputs\n Kind: string\n Description: Endpoint canonico usado para resolver schemaUrl, readUrl e submitUrl relativos em hosts destacados.\n\nComponent Capability:\n - Path: inputs.submitUrl\n Category: inputs\n Kind: string\n Description: Endpoint canonico de submit publicado por surface/action.\n\nComponent Capability:\n - Path: inputs.submitMethod\n Category: inputs\n Kind: enum\n Description: Metodo HTTP canonico para o submit quando o backend publica operacao explicita.\n\nComponent Capability:\n - Path: inputs.submitIdempotencyKey\n Category: inputs\n Kind: string\n Description: Identidade estavel da tentativa de comando, materializada a partir da politica execution da action.\n\nComponent Capability:\n - Path: inputs.submitCorrelationId\n Category: inputs\n Kind: string\n Description: Identificador de correlacao governado para rastreabilidade ponta a ponta do comando.\n\nComponent Capability:\n - Path: inputs.submitResourceVersion\n Category: inputs\n Kind: string\n Description: Versao persistida esperada, transportada como If-Match quando exigida pela action.\n\nComponent Capability:\n - Path: inputs.responseSchemaUrl\n Category: inputs\n Kind: string\n Description: Schema de resposta publicado pelo backend para inspeção diagnostica/read-only do host.\n\nComponent Capability:\n - Path: inputs.enableCustomization\n Category: inputs\n Kind: boolean\n Description: Habilita a edicao de layout/configuracao no editor.\n\nComponent Capability:\n - Path: inputs.showAiAssistant\n Category: inputs\n Kind: boolean\n Description: Mostra os gatilhos do copiloto semantico Praxis para a instancia do formulario.\n\nComponent Capability:\n - Path: inputs.formId\n Category: inputs\n Kind: string\n Description: ID persistente para salvar configuracoes e preferencias.\n\nComponent Capability:\n - Path: inputs.editorialContext\n Category: inputs\n Kind: object\n Description: Override de contexto editorial fornecido pelo host para os blocos hospedados antes/depois do formulario.\n\nComponent Capability:\n - Path: inputs.layout\n Category: inputs\n Kind: object\n Description: Layout legado (FormLayout). Preferir inputs.config + sections.\n\nComponent Capability:\n - Path: inputs.generatedLayoutPreset\n Category: inputs\n Kind: enum\n Description: Preset usado somente na criacao inicial de FormConfig a partir de schema/metadata.\n\nComponent Capability:\n - Path: inputs.configPersistenceStrategy\n Category: inputs\n Kind: enum\n Description: Define se preferências locais persistidas ou o contrato explícito do host governam a materialização corrente.\n\nComponent Capability:\n - Path: inputs.layoutPolicy\n Category: inputs\n Kind: object\n Description: Politica opt-in para materializar layout diretamente do schema atual sem depender de FormConfig.sections authoradas.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.source\n Category: inputs\n Kind: enum\n Description: Define se o layout vem de configuracao authorada ou do schema.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.preset\n Category: inputs\n Kind: enum\n Description: Preset de layout schema-driven. compactPresentation cobre detalhe read-only; groupedCommand cobre create/edit.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.intent\n Category: inputs\n Kind: enum\n Description: Intencao da surface: detalhe/apresentacao ou comando/editavel.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.lifecycle\n Category: inputs\n Kind: enum\n Description: Ciclo de materializacao do layout schema-driven.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.persistence\n Category: inputs\n Kind: enum\n Description: Define se sections geradas sao transient ou podem virar layout authorado apos detach explicito.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.groupedCommand.orphanFieldExpansion\n Category: inputs\n Kind: enum\n Description: Define como uma linha groupedCommand com um unico campo usa o espaco restante. O default medium-and-wide expande spans 6 ou 8 e preserva campos compactos.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.groupedCommand.contextFields\n Category: inputs\n Kind: array\n Description: Campos canônicos do request cujo valor vem do contexto da surface. Eles permanecem no FormGroup e no payload, sem renderizar controles redundantes.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary\n Category: inputs\n Kind: object\n Description: Politica declarativa para resumo read-only compacto em compactPresentation, sem FormConfig.sections locais.",
12187
12223
  "sourcePointer": "projects/praxis-dynamic-form/src/lib/ai/form-ai-capabilities.ts",
12188
- "contentHash": "e89e40f565c3d190defcdd4bb253f57ddde076effae96d6b2a0c9d85aa2cc82f",
12224
+ "contentHash": "c0a08ef89a122e2e5ec62606017281c689a4e7e35662112e4ea5c00dbd4e3055",
12189
12225
  "sourceKind": "component_definition",
12190
12226
  "sourceId": "praxis-dynamic-form-dialog-host",
12191
12227
  "corpusVersion": "1.0.0"
@@ -12193,9 +12229,9 @@
12193
12229
  {
12194
12230
  "chunkIndex": 11,
12195
12231
  "chunkKind": "capabilities",
12196
- "content": "Component Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeRoles\n Category: inputs\n Kind: array\n Description: Lista de papeis semanticos de campo que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.maxFields\n Category: inputs\n Kind: number\n Description: Limite maximo de campos materializados no primeiro painel compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.fieldPriority\n Category: inputs\n Kind: object\n Description: Mapa campo -> prioridade para ordenar campos do resumo quando o schema publica candidatos suficientes.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.groupPriority\n Category: inputs\n Kind: object\n Description: Mapa grupo -> prioridade para ordenar grupos do resumo quando o schema publica agrupamento semantico.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.columns\n Category: inputs\n Kind: number\n Description: Quantidade base de colunas para distribuir campos de resumo schema-driven sem criar FormConfig.sections locais.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.responsiveColumns\n Category: inputs\n Kind: object\n Description: Mapa de colunas por breakpoint xs/sm/md/lg/xl para detalhes read-only compactos em drawers, side sheets e desktop largo.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.widthPrecedence\n Category: inputs\n Kind: enum\n Description: Define se larguras explicitas do schema ou a densidade do resumo controlam os spans no compactPresentation read-only.\n\nComponent Capability:\n - Path: inputs.fieldIconPolicy\n Category: inputs\n Kind: enum\n Description: Controla se icones prefix/suffix dos metadados aparecem em todos os modos, apenas apresentacao, ou nunca.\n\nComponent Capability:\n - Path: inputs.backConfig\n Category: inputs\n Kind: object\n Description: Configuracao de navegacao/retorno (BackConfig).\n\nComponent Capability:\n - Path: inputs.backConfig.strategy\n Category: inputs\n Kind: enum\n Description: Estrategia de retorno (auto/close/navigate).\n\nComponent Capability:\n - Path: inputs.backConfig.returnTo\n Category: inputs\n Kind: string\n Description: Rota para retornar quando strategy=navigate.\n\nComponent Capability:\n - Path: inputs.backConfig.confirmOnDirty\n Category: inputs\n Kind: boolean\n Description: Solicita confirmacao ao sair com mudancas nao salvas.\n\nComponent Capability:\n - Path: inputs.hooks\n Category: inputs\n Kind: object\n Description: Declaracoes de hooks fornecidas diretamente ao componente.\n\nComponent Capability:\n - Path: inputs.hooks.beforeInit[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes da inicializacao.\n\nComponent Capability:\n - Path: inputs.hooks.afterInit[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos a inicializacao.\n\nComponent Capability:\n - Path: inputs.hooks.beforeValidate[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes da validacao.\n\nComponent Capability:\n - Path: inputs.hooks.afterValidate[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos a validacao.\n\nComponent Capability:\n - Path: inputs.hooks.beforeSubmit[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes do submit.\n\nComponent Capability:\n - Path: inputs.hooks.afterSubmit[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos o submit.\n\nComponent Capability:\n - Path: inputs.hooks.onError[]\n Category: hooks\n Kind: array\n Description: Hooks executados em erros de submit/validacao.\n\nComponent Capability:\n - Path: inputs.removeEmptyContainersOnSave\n Category: inputs\n Kind: boolean\n Description: Remove containers vazios ao salvar configuracao.\n\nComponent Capability:\n - Path: inputs.reactiveValidation\n Category: inputs\n Kind: boolean\n Description: Ativa validacao reativa (afterValidate em valueChanges).\n\nComponent Capability:\n - Path: inputs.reactiveValidationDebounceMs\n Category: inputs\n Kind: number\n Description: Debounce em ms para validacao reativa.\n\nComponent Capability:\n - Path: inputs.notifyIfOutdated\n Category: inputs\n Kind: enum\n Description: Canal de notificacao para schema desatualizado.\n\nComponent Capability:\n - Path: inputs.snoozeMs\n Category: inputs\n Kind: number\n Description: Duracao do snooze para alertas de schema.\n\nComponent Capability:\n - Path: inputs.autoOpenSettingsOnOutdated\n Category: inputs\n Kind: boolean\n Description: Abre automaticamente o editor ao detectar schema desatualizado.\n\nComponent Capability:\n - Path: inputs.readonlyModeGlobal\n Category: inputs\n Kind: boolean\n Description: Forca modo somente leitura global.\n\nComponent Capability:\n - Path: inputs.disabledModeGlobal\n Category: inputs\n Kind: boolean\n Description: Forca modo desabilitado global.\n\nComponent Capability:\n - Path: inputs.presentationModeGlobal\n Category: inputs\n Kind: boolean\n Description: Ativa modo de apresentacao global (somente view).\n\nComponent Capability:\n - Path: inputs.visibleGlobal\n Category: inputs\n Kind: boolean\n Description: Forca visibilidade global dos campos.\n\nComponent Capability:\n - Path: inputs.customEndpoints\n Category: inputs\n Kind: object\n Description: Override de endpoints CRUD (EndpointConfig).\n\nComponent Capability:\n - Path: inputs.customEndpoints.getAll\n Category: inputs\n Kind: string\n Description: Endpoint customizado para listar registros.\n\nComponent Capability:\n - Path: inputs.customEndpoints.getById\n Category: inputs\n Kind: string\n Description: Endpoint customizado para obter registro por ID.\n\nComponent Capability:\n - Path: inputs.customEndpoints.create\n Category: inputs\n Kind: string\n Description: Endpoint customizado para criar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.update\n Category: inputs\n Kind: string\n Description: Endpoint customizado para atualizar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.delete\n Category: inputs\n Kind: string\n Description: Endpoint customizado para deletar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.filter\n Category: inputs\n Kind: string\n Description: Endpoint customizado para filtro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.schema\n Category: inputs\n Kind: string\n Description: Endpoint customizado para schemas.\n\nComponent Capability:\n - Path: inputs.config.presentation\n Category: appearance\n Kind: object\n Description: Preferencias visuais persistidas no config.\n\nComponent Capability:\n - Path: inputs.config.presentation.labelFontSize\n Category: appearance\n Kind: number\n Description: Tamanho da fonte do label (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.valueFontSize\n Category: appearance\n Kind: number\n Description: Tamanho da fonte do valor (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.labelPosition\n Category: appearance\n Kind: enum\n Description: Posicionamento do label (above/left).\n\nComponent Capability:\n - Path: inputs.config.presentation.compact\n Category: appearance\n Kind: boolean\n Description: Modo compacto de apresentacao.\n\nComponent Capability:\n - Path: inputs.config.presentation.density\n Category: appearance\n Kind: enum\n Description: Densidade visual global.\n\nComponent Capability:\n - Path: inputs.config.presentation.labelWidth\n Category: appearance\n Kind: number\n Description: Largura do label (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.labelAlign\n Category: appearance\n Kind: enum\n Description: Alinhamento do label.",
12232
+ "content": "Component Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeFields\n Category: inputs\n Kind: array\n Description: Lista de campos canônicos do schema que devem entrar no resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeFields\n Category: inputs\n Kind: array\n Description: Lista de campos canônicos do schema que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeGroups\n Category: inputs\n Kind: array\n Description: Lista de grupos do schema que devem entrar no resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeGroups\n Category: inputs\n Kind: array\n Description: Lista de grupos do schema que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.includeRoles\n Category: inputs\n Kind: array\n Description: Lista de papeis semanticos de campo que devem entrar no resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.excludeRoles\n Category: inputs\n Kind: array\n Description: Lista de papeis semanticos de campo que devem ficar fora do resumo read-only compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.maxFields\n Category: inputs\n Kind: number\n Description: Limite maximo de campos materializados no primeiro painel compacto.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.fieldPriority\n Category: inputs\n Kind: object\n Description: Mapa campo -> prioridade para ordenar campos do resumo quando o schema publica candidatos suficientes.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.groupPriority\n Category: inputs\n Kind: object\n Description: Mapa grupo -> prioridade para ordenar grupos do resumo quando o schema publica agrupamento semantico.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.columns\n Category: inputs\n Kind: number\n Description: Quantidade base de colunas para distribuir campos de resumo schema-driven sem criar FormConfig.sections locais.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.responsiveColumns\n Category: inputs\n Kind: object\n Description: Mapa de colunas por breakpoint xs/sm/md/lg/xl para detalhes read-only compactos em drawers, side sheets e desktop largo.\n\nComponent Capability:\n - Path: inputs.layoutPolicy.detailSummary.widthPrecedence\n Category: inputs\n Kind: enum\n Description: Define se larguras explicitas do schema ou a densidade do resumo controlam os spans no compactPresentation read-only.\n\nComponent Capability:\n - Path: inputs.fieldIconPolicy\n Category: inputs\n Kind: enum\n Description: Controla se icones prefix/suffix dos metadados aparecem em todos os modos, apenas apresentacao, ou nunca.\n\nComponent Capability:\n - Path: inputs.backConfig\n Category: inputs\n Kind: object\n Description: Configuracao de navegacao/retorno (BackConfig).\n\nComponent Capability:\n - Path: inputs.backConfig.strategy\n Category: inputs\n Kind: enum\n Description: Estrategia de retorno (auto/close/navigate).\n\nComponent Capability:\n - Path: inputs.backConfig.returnTo\n Category: inputs\n Kind: string\n Description: Rota para retornar quando strategy=navigate.\n\nComponent Capability:\n - Path: inputs.backConfig.confirmOnDirty\n Category: inputs\n Kind: boolean\n Description: Solicita confirmacao ao sair com mudancas nao salvas.\n\nComponent Capability:\n - Path: inputs.hooks\n Category: inputs\n Kind: object\n Description: Declaracoes de hooks fornecidas diretamente ao componente.\n\nComponent Capability:\n - Path: inputs.hooks.beforeInit[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes da inicializacao.\n\nComponent Capability:\n - Path: inputs.hooks.afterInit[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos a inicializacao.\n\nComponent Capability:\n - Path: inputs.hooks.beforeValidate[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes da validacao.\n\nComponent Capability:\n - Path: inputs.hooks.afterValidate[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos a validacao.\n\nComponent Capability:\n - Path: inputs.hooks.beforeSubmit[]\n Category: hooks\n Kind: array\n Description: Hooks executados antes do submit.\n\nComponent Capability:\n - Path: inputs.hooks.afterSubmit[]\n Category: hooks\n Kind: array\n Description: Hooks executados apos o submit.\n\nComponent Capability:\n - Path: inputs.hooks.onError[]\n Category: hooks\n Kind: array\n Description: Hooks executados em erros de submit/validacao.\n\nComponent Capability:\n - Path: inputs.removeEmptyContainersOnSave\n Category: inputs\n Kind: boolean\n Description: Remove containers vazios ao salvar configuracao.\n\nComponent Capability:\n - Path: inputs.reactiveValidation\n Category: inputs\n Kind: boolean\n Description: Ativa validacao reativa (afterValidate em valueChanges).\n\nComponent Capability:\n - Path: inputs.reactiveValidationDebounceMs\n Category: inputs\n Kind: number\n Description: Debounce em ms para validacao reativa.\n\nComponent Capability:\n - Path: inputs.notifyIfOutdated\n Category: inputs\n Kind: enum\n Description: Canal de notificacao para schema desatualizado.\n\nComponent Capability:\n - Path: inputs.snoozeMs\n Category: inputs\n Kind: number\n Description: Duracao do snooze para alertas de schema.\n\nComponent Capability:\n - Path: inputs.autoOpenSettingsOnOutdated\n Category: inputs\n Kind: boolean\n Description: Abre automaticamente o editor ao detectar schema desatualizado.\n\nComponent Capability:\n - Path: inputs.readonlyModeGlobal\n Category: inputs\n Kind: boolean\n Description: Forca modo somente leitura global.\n\nComponent Capability:\n - Path: inputs.disabledModeGlobal\n Category: inputs\n Kind: boolean\n Description: Forca modo desabilitado global.\n\nComponent Capability:\n - Path: inputs.presentationModeGlobal\n Category: inputs\n Kind: boolean\n Description: Ativa modo de apresentacao global (somente view).\n\nComponent Capability:\n - Path: inputs.visibleGlobal\n Category: inputs\n Kind: boolean\n Description: Forca visibilidade global dos campos.\n\nComponent Capability:\n - Path: inputs.customEndpoints\n Category: inputs\n Kind: object\n Description: Override de endpoints CRUD (EndpointConfig).\n\nComponent Capability:\n - Path: inputs.customEndpoints.getAll\n Category: inputs\n Kind: string\n Description: Endpoint customizado para listar registros.\n\nComponent Capability:\n - Path: inputs.customEndpoints.getById\n Category: inputs\n Kind: string\n Description: Endpoint customizado para obter registro por ID.\n\nComponent Capability:\n - Path: inputs.customEndpoints.create\n Category: inputs\n Kind: string\n Description: Endpoint customizado para criar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.update\n Category: inputs\n Kind: string\n Description: Endpoint customizado para atualizar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.delete\n Category: inputs\n Kind: string\n Description: Endpoint customizado para deletar registro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.filter\n Category: inputs\n Kind: string\n Description: Endpoint customizado para filtro.\n\nComponent Capability:\n - Path: inputs.customEndpoints.schema\n Category: inputs\n Kind: string\n Description: Endpoint customizado para schemas.\n\nComponent Capability:\n - Path: inputs.config.presentation\n Category: appearance\n Kind: object\n Description: Preferencias visuais persistidas no config.",
12197
12233
  "sourcePointer": "projects/praxis-dynamic-form/src/lib/ai/form-ai-capabilities.ts",
12198
- "contentHash": "3201790d3d1d9216e42135caea0bdbc3ff94f7c46bbeb54a48bb5d700c53d8ff",
12234
+ "contentHash": "e04d360e46c5cf6d9856c9b55a6f140cc7a2fa07a670575bfe1319fd5079b158",
12199
12235
  "sourceKind": "component_definition",
12200
12236
  "sourceId": "praxis-dynamic-form-dialog-host",
12201
12237
  "corpusVersion": "1.0.0"
@@ -12203,9 +12239,9 @@
12203
12239
  {
12204
12240
  "chunkIndex": 12,
12205
12241
  "chunkKind": "capabilities",
12206
- "content": "Component Capability:\n - Path: inputs.config.presentation.valueAlign\n Category: appearance\n Kind: enum\n Description: Alinhamento do valor.\n\nComponent Capability:\n - Path: outputs.formSubmit\n Category: outputs\n Kind: object\n Description: Evento emitido no submit do formulario.\n\nComponent Capability:\n - Path: outputs.formCancel\n Category: outputs\n Kind: object\n Description: Evento emitido ao cancelar.\n\nComponent Capability:\n - Path: outputs.formReset\n Category: outputs\n Kind: object\n Description: Evento emitido ao resetar o formulario.\n\nComponent Capability:\n - Path: outputs.configChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando a configuracao muda.\n\nComponent Capability:\n - Path: outputs.formReady\n Category: outputs\n Kind: object\n Description: Evento emitido quando o formulario esta pronto.\n\nComponent Capability:\n - Path: outputs.valueChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o valor do formulario muda.\n\nComponent Capability:\n - Path: outputs.syncCompleted\n Category: outputs\n Kind: object\n Description: Evento emitido ao completar sincronizacao com schema.\n\nComponent Capability:\n - Path: outputs.initializationError\n Category: outputs\n Kind: object\n Description: Evento emitido quando ocorre erro na inicializacao.\n\nComponent Capability:\n - Path: outputs.enableCustomizationChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o modo de customizacao muda.\n\nComponent Capability:\n - Path: outputs.customAction\n Category: outputs\n Kind: object\n Description: Evento emitido ao disparar acao customizada.\n\nComponent Capability:\n - Path: outputs.actionConfirmation\n Category: outputs\n Kind: object\n Description: Evento emitido quando acao requer confirmacao.\n\nComponent Capability:\n - Path: outputs.schemaStatusChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o status do schema muda.\n\nCapability Note:\n - Este catalogo descreve inputs e outputs do componente.\n\nCapability Note:\n - O objeto inputs.config segue o schema de FormConfig (veja form-ai-capabilities).\n\nCapability Note:\n - inputs.config.presentation e uma extensao de runtime aplicada pelo componente.\n\nCapability Note:\n - Preferir FormConfig (sections/fieldMetadata) em vez do layout legado (fieldsets).\n\nCapability Note:\n - Use inputs.editorialContext apenas para override de contexto editorial do host; a precedencia final e runtime < config.editorialContext < host.\n\nCapability Note:\n - rich content editorial hospedado pelo form pode ser renderizado em tres slots semanticos: before, beforeActions e after.",
12242
+ "content": "Component Capability:\n - Path: inputs.config.presentation.labelFontSize\n Category: appearance\n Kind: number\n Description: Tamanho da fonte do label (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.valueFontSize\n Category: appearance\n Kind: number\n Description: Tamanho da fonte do valor (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.labelPosition\n Category: appearance\n Kind: enum\n Description: Posicionamento do label (above/left).\n\nComponent Capability:\n - Path: inputs.config.presentation.compact\n Category: appearance\n Kind: boolean\n Description: Modo compacto de apresentacao.\n\nComponent Capability:\n - Path: inputs.config.presentation.density\n Category: appearance\n Kind: enum\n Description: Densidade visual global.\n\nComponent Capability:\n - Path: inputs.config.presentation.labelWidth\n Category: appearance\n Kind: number\n Description: Largura do label (px).\n\nComponent Capability:\n - Path: inputs.config.presentation.labelAlign\n Category: appearance\n Kind: enum\n Description: Alinhamento do label.\n\nComponent Capability:\n - Path: inputs.config.presentation.valueAlign\n Category: appearance\n Kind: enum\n Description: Alinhamento do valor.\n\nComponent Capability:\n - Path: outputs.formSubmit\n Category: outputs\n Kind: object\n Description: Evento emitido no submit do formulario.\n\nComponent Capability:\n - Path: outputs.formCancel\n Category: outputs\n Kind: object\n Description: Evento emitido ao cancelar.\n\nComponent Capability:\n - Path: outputs.formReset\n Category: outputs\n Kind: object\n Description: Evento emitido ao resetar o formulario.\n\nComponent Capability:\n - Path: outputs.configChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando a configuracao muda.\n\nComponent Capability:\n - Path: outputs.formReady\n Category: outputs\n Kind: object\n Description: Evento emitido quando o formulario esta pronto.\n\nComponent Capability:\n - Path: outputs.valueChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o valor do formulario muda.\n\nComponent Capability:\n - Path: outputs.syncCompleted\n Category: outputs\n Kind: object\n Description: Evento emitido ao completar sincronizacao com schema.\n\nComponent Capability:\n - Path: outputs.initializationError\n Category: outputs\n Kind: object\n Description: Evento emitido quando ocorre erro na inicializacao.\n\nComponent Capability:\n - Path: outputs.enableCustomizationChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o modo de customizacao muda.\n\nComponent Capability:\n - Path: outputs.customAction\n Category: outputs\n Kind: object\n Description: Evento emitido ao disparar acao customizada.\n\nComponent Capability:\n - Path: outputs.actionConfirmation\n Category: outputs\n Kind: object\n Description: Evento emitido quando acao requer confirmacao.\n\nComponent Capability:\n - Path: outputs.schemaStatusChange\n Category: outputs\n Kind: object\n Description: Evento emitido quando o status do schema muda.\n\nCapability Note:\n - Este catalogo descreve inputs e outputs do componente.\n\nCapability Note:\n - O objeto inputs.config segue o schema de FormConfig (veja form-ai-capabilities).\n\nCapability Note:\n - inputs.config.presentation e uma extensao de runtime aplicada pelo componente.\n\nCapability Note:\n - Preferir FormConfig (sections/fieldMetadata) em vez do layout legado (fieldsets).\n\nCapability Note:\n - Use inputs.editorialContext apenas para override de contexto editorial do host; a precedencia final e runtime < config.editorialContext < host.\n\nCapability Note:\n - rich content editorial hospedado pelo form pode ser renderizado em tres slots semanticos: before, beforeActions e after.",
12207
12243
  "sourcePointer": "projects/praxis-dynamic-form/src/lib/ai/form-ai-capabilities.ts",
12208
- "contentHash": "0e9fb6b721474a0c25bacf19e9b00e48e90c790f721745ed2438da208ad4a9a0",
12244
+ "contentHash": "beda7b0e56767eb05d2104f64b078e5d582354da389421f4cc5db0853bf87a2a",
12209
12245
  "sourceKind": "component_definition",
12210
12246
  "sourceId": "praxis-dynamic-form-dialog-host",
12211
12247
  "corpusVersion": "1.0.0"
@@ -103,6 +103,7 @@ class CrudLauncherService {
103
103
  storage = inject(ASYNC_CONFIG_STORAGE);
104
104
  global = inject(GlobalConfigService);
105
105
  operationResolver = inject(CrudOperationResolutionService);
106
+ dialogHostPromise;
106
107
  drawerAdapter = (() => {
107
108
  try {
108
109
  return inject(CRUD_DRAWER_ADAPTER);
@@ -112,6 +113,9 @@ class CrudLauncherService {
112
113
  }
113
114
  })();
114
115
  async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
116
+ const prefetchedDialogHost = this.isLikelyOverlayAction(action, metadata)
117
+ ? this.loadDialogHost()
118
+ : undefined;
115
119
  // Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
116
120
  const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
117
121
  merged.action = this.normalizeActionForLaunch(merged.action);
@@ -177,7 +181,7 @@ class CrudLauncherService {
177
181
  backdropClasses.push('pfx-transparent-backdrop');
178
182
  }
179
183
  const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
180
- const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
184
+ const ref = await this.dialog.openAsync(() => prefetchedDialogHost ?? this.loadDialogHost(), {
181
185
  ...modalCfg,
182
186
  panelClass: panelClasses,
183
187
  backdropClass: mergedBackdropClasses,
@@ -256,6 +260,13 @@ class CrudLauncherService {
256
260
  return 'route';
257
261
  }
258
262
  }
263
+ isLikelyOverlayAction(action, metadata) {
264
+ const configuredMode = action.openMode ?? metadata.defaults?.openMode;
265
+ return configuredMode === 'drawer' || configuredMode === 'modal';
266
+ }
267
+ loadDialogHost() {
268
+ return this.dialogHostPromise ??= Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((module) => module.DynamicFormDialogHostComponent);
269
+ }
259
270
  buildRoute(action, row, metadata) {
260
271
  let route = action.route;
261
272
  const query = {};
@@ -896,6 +907,9 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
896
907
  'crud.actions.view': 'Ver',
897
908
  'crud.actions.edit': 'Editar',
898
909
  'crud.actions.delete': 'Excluir',
910
+ 'crud.actions.default': 'ação',
911
+ 'crud.actions.opening': 'Abrindo {action}…',
912
+ 'crud.surface.openFailed': 'Não foi possível abrir {title}. Tente novamente ou confirme se esse recurso continua disponível.',
899
913
  'crud.dialog.recordContextLabel': 'Registro em edição',
900
914
  'crud.delete.confirmMessage': 'Esta ação não pode ser desfeita. Deseja continuar?',
901
915
  'crud.delete.cancel': 'Cancelar',
@@ -914,6 +928,9 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
914
928
  'crud.actions.view': 'View',
915
929
  'crud.actions.edit': 'Edit',
916
930
  'crud.actions.delete': 'Delete',
931
+ 'crud.actions.default': 'action',
932
+ 'crud.actions.opening': 'Opening {action}…',
933
+ 'crud.surface.openFailed': 'Could not open {title}. Try again or confirm that this resource is still available.',
917
934
  'crud.dialog.recordContextLabel': 'Record being edited',
918
935
  'crud.delete.confirmMessage': 'This action cannot be undone. Do you want to continue?',
919
936
  'crud.delete.cancel': 'Cancel',
@@ -3664,6 +3681,7 @@ class PraxisCrudComponent {
3664
3681
  tableQueryContext = null;
3665
3682
  tableFilterCriteria = {};
3666
3683
  tableCrudContext;
3684
+ openingActionLabel = signal(null, ...(ngDevMode ? [{ debugName: "openingActionLabel" }] : /* istanbul ignore next */ []));
3667
3685
  launcher = inject(CrudLauncherService);
3668
3686
  http = inject(HttpClient);
3669
3687
  destroyRef = inject(DestroyRef);
@@ -3815,8 +3833,11 @@ class PraxisCrudComponent {
3815
3833
  }
3816
3834
  }
3817
3835
  async onAction(action, row, runtimeEvent) {
3836
+ if (this.openingActionLabel()) {
3837
+ return;
3838
+ }
3839
+ this.openingActionLabel.set(this.resolveOpeningActionLabel(action, runtimeEvent));
3818
3840
  try {
3819
- document.activeElement?.blur();
3820
3841
  const normalizedAction = this.normalizeCrudActionName(action);
3821
3842
  const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
3822
3843
  let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
@@ -3921,6 +3942,26 @@ class PraxisCrudComponent {
3921
3942
  catch (err) {
3922
3943
  this.error.emit(err);
3923
3944
  }
3945
+ finally {
3946
+ this.openingActionLabel.set(null);
3947
+ }
3948
+ }
3949
+ getOpeningActionStatus(actionLabel) {
3950
+ return this.txWithParams('crud.actions.opening', 'Abrindo {action}…', { action: actionLabel });
3951
+ }
3952
+ resolveOpeningActionLabel(action, runtimeEvent) {
3953
+ const configuredLabel = String(runtimeEvent?.actionConfig?.label || '').trim();
3954
+ if (configuredLabel) {
3955
+ return configuredLabel;
3956
+ }
3957
+ const normalized = this.normalizeCrudActionName(action);
3958
+ if (normalized === 'create'
3959
+ || normalized === 'view'
3960
+ || normalized === 'edit'
3961
+ || normalized === 'delete') {
3962
+ return this.getCrudActionLabel(normalized);
3963
+ }
3964
+ return String(action || '').trim() || this.tx('crud.actions.default', 'ação');
3924
3965
  }
3925
3966
  hasExplicitOpenBinding(action) {
3926
3967
  const mode = action.openMode;
@@ -4335,9 +4376,6 @@ class PraxisCrudComponent {
4335
4376
  return false;
4336
4377
  }
4337
4378
  const providedSurface = this.resolveProvidedSurface(normalizedAction, this.extractDiscoveryActionConfig(runtimeEvent));
4338
- if (!providedSurface && !this.isDiscoveryManagedCrudAction(normalizedAction)) {
4339
- return false;
4340
- }
4341
4379
  const catalog = providedSurface
4342
4380
  ? null
4343
4381
  : await this.resolveDiscoveredSurfaceCatalog(normalizedAction, row);
@@ -4358,10 +4396,15 @@ class PraxisCrudComponent {
4358
4396
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
4359
4397
  apiUrlEntry: this.resolveDiscoveryApiEntry(),
4360
4398
  group: catalog?.group ?? null,
4399
+ presentation: 'drawer',
4400
+ title: surface.title,
4401
+ subtitle: surface.description ?? undefined,
4361
4402
  });
4362
4403
  }
4363
- catch {
4364
- return false;
4404
+ catch (error) {
4405
+ this.error.emit(error);
4406
+ this.snack.open(this.txWithParams('crud.surface.openFailed', 'Não foi possível abrir {title}. Tente novamente ou confirme se esse recurso continua disponível.', { title: surface.title || surface.id }), undefined, { duration: 4500 });
4407
+ return true;
4365
4408
  }
4366
4409
  let openPromise;
4367
4410
  try {
@@ -4393,9 +4436,12 @@ class PraxisCrudComponent {
4393
4436
  mode: this.mapSurfacePresentationToCrudMode(payload?.presentation),
4394
4437
  action,
4395
4438
  });
4396
- void openPromise
4397
- .then((result) => this.handleDiscoveredSurfaceResult(result))
4398
- .catch((err) => this.error.emit(err));
4439
+ try {
4440
+ this.bindDiscoveredSurfaceLifecycle(await openPromise);
4441
+ }
4442
+ catch (err) {
4443
+ this.error.emit(err);
4444
+ }
4399
4445
  return true;
4400
4446
  }
4401
4447
  async resolveDiscoveredSurfaceCatalog(action, row) {
@@ -4518,13 +4564,13 @@ class PraxisCrudComponent {
4518
4564
  }
4519
4565
  return catalogCandidate;
4520
4566
  }
4521
- resolveProvidedSurface(action, candidate) {
4567
+ resolveProvidedSurface(_action, candidate) {
4522
4568
  if (!candidate || typeof candidate !== 'object') {
4523
4569
  return null;
4524
4570
  }
4525
4571
  const catalogCandidate = candidate;
4526
4572
  const normalizedId = String(catalogCandidate.id || '').trim().toLowerCase();
4527
- if (!normalizedId || normalizedId !== action) {
4573
+ if (!normalizedId) {
4528
4574
  return null;
4529
4575
  }
4530
4576
  const surface = catalogCandidate;
@@ -4540,6 +4586,10 @@ class PraxisCrudComponent {
4540
4586
  const candidates = surfaces
4541
4587
  .filter((surface) => surface.availability?.allowed !== false)
4542
4588
  .sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
4589
+ const exact = candidates.find((surface) => String(surface.id || '').trim().toLowerCase() === action);
4590
+ if (exact) {
4591
+ return exact;
4592
+ }
4543
4593
  const preferredIds = this.getPreferredSurfaceIdsForCrudAction(action);
4544
4594
  const preferred = candidates.find((surface) => preferredIds.includes(String(surface.id || '').trim().toLowerCase()));
4545
4595
  if (preferred) {
@@ -4600,7 +4650,6 @@ class PraxisCrudComponent {
4600
4650
  return presentation === 'modal' ? 'modal' : 'drawer';
4601
4651
  }
4602
4652
  handleDiscoveredSurfaceResult(result) {
4603
- this.afterClose.emit();
4604
4653
  const data = (result?.data || {});
4605
4654
  const idField = this.getIdField();
4606
4655
  if (result?.type === 'save') {
@@ -4614,6 +4663,34 @@ class PraxisCrudComponent {
4614
4663
  this.refreshTable();
4615
4664
  }
4616
4665
  }
4666
+ bindDiscoveredSurfaceLifecycle(ref) {
4667
+ const surfaceRef = ref;
4668
+ if (!surfaceRef)
4669
+ return;
4670
+ let terminalResultHandled = false;
4671
+ let closeHandled = false;
4672
+ const handleResult = (result) => {
4673
+ const type = String(result?.type || '');
4674
+ if (terminalResultHandled || (type !== 'save' && type !== 'delete'))
4675
+ return;
4676
+ terminalResultHandled = true;
4677
+ this.handleDiscoveredSurfaceResult(result);
4678
+ };
4679
+ surfaceRef.result$?.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(handleResult);
4680
+ const handleClose = (result) => {
4681
+ handleResult(result);
4682
+ if (closeHandled)
4683
+ return;
4684
+ closeHandled = true;
4685
+ this.afterClose.emit();
4686
+ };
4687
+ if (surfaceRef.closed$) {
4688
+ surfaceRef.closed$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(handleClose);
4689
+ }
4690
+ else {
4691
+ surfaceRef.afterClosed?.().pipe(takeUntilDestroyed(this.destroyRef)).subscribe(handleClose);
4692
+ }
4693
+ }
4617
4694
  buildEffectiveTableConfig(meta, capabilities) {
4618
4695
  const base = meta.table;
4619
4696
  const current = this.getCurrentTableConfigSnapshot();
@@ -5161,38 +5238,46 @@ class PraxisCrudComponent {
5161
5238
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
5162
5239
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5163
5240
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
5164
- @if (shouldRenderTable(resolvedMetadata)) {
5165
- <praxis-table
5166
- [config]="tableConfigForBinding"
5167
- [resourcePath]="resolveResourcePath(resolvedMetadata)"
5168
- [data]="resolveTableData(resolvedMetadata)"
5169
- [queryContext]="tableQueryContext"
5170
- [filterCriteria]="tableFilterCriteria"
5171
- [tableId]="crudId || 'default'"
5172
- [crudContext]="tableCrudContext"
5173
- [enableCustomization]="enableCustomization"
5174
- [authoringCapability]="authoringCapability"
5175
- (rowClick)="onTableRowClick($event)"
5176
- (selectionChange)="onTableSelectionChange($event)"
5177
- (rowAction)="onAction($event.action, $event.row, $event)"
5178
- (toolbarAction)="onToolbarAction($event)"
5179
- (bulkAction)="onBulkAction($event)"
5180
- (collectionLinksChange)="onCollectionLinksChange($event)"
5181
- (reset)="onResetPreferences()"
5182
- (metadataChange)="onTableMetadataChange()"
5183
- (loadingStateChange)="onTableLoadingStateChange($event)"
5184
- ></praxis-table>
5185
- } @else {
5186
- @if (isCustomizationAvailable()) {
5187
- <praxis-empty-state-card
5188
- icon="table_rows"
5189
- [title]="getEmptyStateTitle()"
5190
- [description]="getEmptyStateDescription()"
5191
- [primaryAction]="{ label: getEmptyStatePrimaryAction(), icon: 'bolt', action: onConfigureRequested.bind(this) }"
5192
- />
5241
+ <div class="praxis-crud-runtime" [attr.aria-busy]="openingActionLabel() ? 'true' : null">
5242
+ @if (openingActionLabel(); as actionLabel) {
5243
+ <div class="praxis-crud-opening-status" role="status" aria-live="polite" data-testid="praxis-crud-opening-status">
5244
+ <span class="praxis-crud-opening-status__spinner" aria-hidden="true"></span>
5245
+ <span>{{ getOpeningActionStatus(actionLabel) }}</span>
5246
+ </div>
5193
5247
  }
5194
- }
5195
- `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "authoringCapability", "dense"], outputs: ["rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone", "variant", "alignment", "density", "iconContainer"] }] });
5248
+ @if (shouldRenderTable(resolvedMetadata)) {
5249
+ <praxis-table
5250
+ [config]="tableConfigForBinding"
5251
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
5252
+ [data]="resolveTableData(resolvedMetadata)"
5253
+ [queryContext]="tableQueryContext"
5254
+ [filterCriteria]="tableFilterCriteria"
5255
+ [tableId]="crudId || 'default'"
5256
+ [crudContext]="tableCrudContext"
5257
+ [enableCustomization]="enableCustomization"
5258
+ [authoringCapability]="authoringCapability"
5259
+ (rowClick)="onTableRowClick($event)"
5260
+ (selectionChange)="onTableSelectionChange($event)"
5261
+ (rowAction)="onAction($event.action, $event.row, $event)"
5262
+ (toolbarAction)="onToolbarAction($event)"
5263
+ (bulkAction)="onBulkAction($event)"
5264
+ (collectionLinksChange)="onCollectionLinksChange($event)"
5265
+ (reset)="onResetPreferences()"
5266
+ (metadataChange)="onTableMetadataChange()"
5267
+ (loadingStateChange)="onTableLoadingStateChange($event)"
5268
+ ></praxis-table>
5269
+ } @else {
5270
+ @if (isCustomizationAvailable()) {
5271
+ <praxis-empty-state-card
5272
+ icon="table_rows"
5273
+ [title]="getEmptyStateTitle()"
5274
+ [description]="getEmptyStateDescription()"
5275
+ [primaryAction]="{ label: getEmptyStatePrimaryAction(), icon: 'bolt', action: onConfigureRequested.bind(this) }"
5276
+ />
5277
+ }
5278
+ }
5279
+ </div>
5280
+ `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}.praxis-crud-runtime{position:relative;min-width:0}.praxis-crud-opening-status{position:absolute;z-index:6;top:.7rem;right:.7rem;display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;max-width:min(24rem,calc(100% - 1.4rem));padding:.48rem .72rem;border:1px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 32%,transparent);border-radius:999px;color:var(--md-sys-color-on-surface, currentColor);background:color-mix(in srgb,var(--md-sys-color-surface-container-high, #fff) 94%,transparent);box-shadow:0 10px 28px #0f172a29;font-size:.82rem;font-weight:650;line-height:1.25;pointer-events:none}.praxis-crud-opening-status__spinner{width:.95rem;height:.95rem;flex:0 0 auto;border:2px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 26%,transparent);border-top-color:var(--md-sys-color-primary, #3f51b5);border-radius:50%;animation:praxis-crud-opening-spin .72s linear infinite}@keyframes praxis-crud-opening-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.praxis-crud-opening-status__spinner{animation:none;border-top-color:currentColor}}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "authoringCapability", "dense"], outputs: ["rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone", "variant", "alignment", "density", "iconContainer"] }] });
5196
5281
  }
5197
5282
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
5198
5283
  type: Component,
@@ -5200,38 +5285,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5200
5285
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
5201
5286
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5202
5287
  ], template: `
5203
- @if (shouldRenderTable(resolvedMetadata)) {
5204
- <praxis-table
5205
- [config]="tableConfigForBinding"
5206
- [resourcePath]="resolveResourcePath(resolvedMetadata)"
5207
- [data]="resolveTableData(resolvedMetadata)"
5208
- [queryContext]="tableQueryContext"
5209
- [filterCriteria]="tableFilterCriteria"
5210
- [tableId]="crudId || 'default'"
5211
- [crudContext]="tableCrudContext"
5212
- [enableCustomization]="enableCustomization"
5213
- [authoringCapability]="authoringCapability"
5214
- (rowClick)="onTableRowClick($event)"
5215
- (selectionChange)="onTableSelectionChange($event)"
5216
- (rowAction)="onAction($event.action, $event.row, $event)"
5217
- (toolbarAction)="onToolbarAction($event)"
5218
- (bulkAction)="onBulkAction($event)"
5219
- (collectionLinksChange)="onCollectionLinksChange($event)"
5220
- (reset)="onResetPreferences()"
5221
- (metadataChange)="onTableMetadataChange()"
5222
- (loadingStateChange)="onTableLoadingStateChange($event)"
5223
- ></praxis-table>
5224
- } @else {
5225
- @if (isCustomizationAvailable()) {
5226
- <praxis-empty-state-card
5227
- icon="table_rows"
5228
- [title]="getEmptyStateTitle()"
5229
- [description]="getEmptyStateDescription()"
5230
- [primaryAction]="{ label: getEmptyStatePrimaryAction(), icon: 'bolt', action: onConfigureRequested.bind(this) }"
5231
- />
5288
+ <div class="praxis-crud-runtime" [attr.aria-busy]="openingActionLabel() ? 'true' : null">
5289
+ @if (openingActionLabel(); as actionLabel) {
5290
+ <div class="praxis-crud-opening-status" role="status" aria-live="polite" data-testid="praxis-crud-opening-status">
5291
+ <span class="praxis-crud-opening-status__spinner" aria-hidden="true"></span>
5292
+ <span>{{ getOpeningActionStatus(actionLabel) }}</span>
5293
+ </div>
5232
5294
  }
5233
- }
5234
- `, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"] }]
5295
+ @if (shouldRenderTable(resolvedMetadata)) {
5296
+ <praxis-table
5297
+ [config]="tableConfigForBinding"
5298
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
5299
+ [data]="resolveTableData(resolvedMetadata)"
5300
+ [queryContext]="tableQueryContext"
5301
+ [filterCriteria]="tableFilterCriteria"
5302
+ [tableId]="crudId || 'default'"
5303
+ [crudContext]="tableCrudContext"
5304
+ [enableCustomization]="enableCustomization"
5305
+ [authoringCapability]="authoringCapability"
5306
+ (rowClick)="onTableRowClick($event)"
5307
+ (selectionChange)="onTableSelectionChange($event)"
5308
+ (rowAction)="onAction($event.action, $event.row, $event)"
5309
+ (toolbarAction)="onToolbarAction($event)"
5310
+ (bulkAction)="onBulkAction($event)"
5311
+ (collectionLinksChange)="onCollectionLinksChange($event)"
5312
+ (reset)="onResetPreferences()"
5313
+ (metadataChange)="onTableMetadataChange()"
5314
+ (loadingStateChange)="onTableLoadingStateChange($event)"
5315
+ ></praxis-table>
5316
+ } @else {
5317
+ @if (isCustomizationAvailable()) {
5318
+ <praxis-empty-state-card
5319
+ icon="table_rows"
5320
+ [title]="getEmptyStateTitle()"
5321
+ [description]="getEmptyStateDescription()"
5322
+ [primaryAction]="{ label: getEmptyStatePrimaryAction(), icon: 'bolt', action: onConfigureRequested.bind(this) }"
5323
+ />
5324
+ }
5325
+ }
5326
+ </div>
5327
+ `, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}.praxis-crud-runtime{position:relative;min-width:0}.praxis-crud-opening-status{position:absolute;z-index:6;top:.7rem;right:.7rem;display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;max-width:min(24rem,calc(100% - 1.4rem));padding:.48rem .72rem;border:1px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 32%,transparent);border-radius:999px;color:var(--md-sys-color-on-surface, currentColor);background:color-mix(in srgb,var(--md-sys-color-surface-container-high, #fff) 94%,transparent);box-shadow:0 10px 28px #0f172a29;font-size:.82rem;font-weight:650;line-height:1.25;pointer-events:none}.praxis-crud-opening-status__spinner{width:.95rem;height:.95rem;flex:0 0 auto;border:2px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 26%,transparent);border-top-color:var(--md-sys-color-primary, #3f51b5);border-radius:50%;animation:praxis-crud-opening-spin .72s linear infinite}@keyframes praxis-crud-opening-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.praxis-crud-opening-status__spinner{animation:none;border-top-color:currentColor}}\n"] }]
5235
5328
  }], ctorParameters: () => [], propDecorators: { metadata: [{
5236
5329
  type: Input,
5237
5330
  args: [{ required: true }]
@@ -5458,6 +5551,22 @@ class DynamicFormDialogHostComponent {
5458
5551
  deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
5459
5552
  this.texts.title;
5460
5553
  }
5554
+ get dialogSubtitle() {
5555
+ const action = this.data.action ?? {};
5556
+ const form = this.formConfig;
5557
+ const resource = this.data.metadata?.resource ?? {};
5558
+ const table = this.data.metadata?.table ?? {};
5559
+ return stringOrUndefined(action.description ??
5560
+ action.tooltip ??
5561
+ form['description'] ??
5562
+ resource.description ??
5563
+ table.subtitle ??
5564
+ table.description);
5565
+ }
5566
+ get dialogIcon() {
5567
+ const action = this.data.action ?? {};
5568
+ return stringOrUndefined(action.icon ?? this.modal.titleIcon ?? this.modal.icon);
5569
+ }
5461
5570
  resolveActionDialogTitle() {
5462
5571
  const actionLabel = stringOrUndefined(this.data.action?.label);
5463
5572
  if (!actionLabel) {
@@ -5709,9 +5818,21 @@ class DynamicFormDialogHostComponent {
5709
5818
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5710
5819
  ], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
5711
5820
  <div mat-dialog-title class="dialog-header">
5712
- <h2 id="crudDialogTitle" class="dialog-title">
5713
- {{ dialogTitle }}
5714
- </h2>
5821
+ @if (dialogIcon; as icon) {
5822
+ <span class="dialog-title-icon" aria-hidden="true">
5823
+ <mat-icon [praxisIcon]="icon"></mat-icon>
5824
+ </span>
5825
+ }
5826
+ <span class="dialog-heading">
5827
+ <h2 id="crudDialogTitle" class="dialog-title">
5828
+ {{ dialogTitle }}
5829
+ </h2>
5830
+ @if (dialogSubtitle; as subtitle) {
5831
+ <span id="crudDialogDescription" class="dialog-subtitle">
5832
+ {{ subtitle }}
5833
+ </span>
5834
+ }
5835
+ </span>
5715
5836
  <span class="spacer"></span>
5716
5837
  @if (modal.canMaximize) {
5717
5838
  <button
@@ -5735,6 +5856,7 @@ class DynamicFormDialogHostComponent {
5735
5856
  <mat-dialog-content
5736
5857
  class="dialog-content"
5737
5858
  aria-labelledby="crudDialogTitle"
5859
+ [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
5738
5860
  >
5739
5861
  @if (resourceIdentity) {
5740
5862
  <div class="crud-resource-identity" data-testid="crud-resource-identity">
@@ -5770,13 +5892,15 @@ class DynamicFormDialogHostComponent {
5770
5892
  (formCancel)="onCancel()"
5771
5893
  ></praxis-dynamic-form>
5772
5894
  </mat-dialog-content>
5773
- `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 64px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
5895
+ `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
5774
5896
  }
5775
5897
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
5776
5898
  type: Component,
5777
5899
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
5778
5900
  MatDialogModule,
5901
+ MatIconModule,
5779
5902
  PraxisIconButtonComponent,
5903
+ PraxisIconDirective,
5780
5904
  PraxisResourceIdentityComponent,
5781
5905
  PraxisDynamicForm
5782
5906
  ], encapsulation: ViewEncapsulation.None, providers: [
@@ -5789,9 +5913,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5789
5913
  '[class.praxis-drawer]': 'presentation === "drawer"',
5790
5914
  }, template: `
5791
5915
  <div mat-dialog-title class="dialog-header">
5792
- <h2 id="crudDialogTitle" class="dialog-title">
5793
- {{ dialogTitle }}
5794
- </h2>
5916
+ @if (dialogIcon; as icon) {
5917
+ <span class="dialog-title-icon" aria-hidden="true">
5918
+ <mat-icon [praxisIcon]="icon"></mat-icon>
5919
+ </span>
5920
+ }
5921
+ <span class="dialog-heading">
5922
+ <h2 id="crudDialogTitle" class="dialog-title">
5923
+ {{ dialogTitle }}
5924
+ </h2>
5925
+ @if (dialogSubtitle; as subtitle) {
5926
+ <span id="crudDialogDescription" class="dialog-subtitle">
5927
+ {{ subtitle }}
5928
+ </span>
5929
+ }
5930
+ </span>
5795
5931
  <span class="spacer"></span>
5796
5932
  @if (modal.canMaximize) {
5797
5933
  <button
@@ -5815,6 +5951,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5815
5951
  <mat-dialog-content
5816
5952
  class="dialog-content"
5817
5953
  aria-labelledby="crudDialogTitle"
5954
+ [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
5818
5955
  >
5819
5956
  @if (resourceIdentity) {
5820
5957
  <div class="crud-resource-identity" data-testid="crud-resource-identity">
@@ -5850,7 +5987,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5850
5987
  (formCancel)="onCancel()"
5851
5988
  ></praxis-dynamic-form>
5852
5989
  </mat-dialog-content>
5853
- `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 64px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{border-radius:0!important}\n"] }]
5990
+ `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"] }]
5854
5991
  }], ctorParameters: () => [{ type: undefined, decorators: [{
5855
5992
  type: Inject,
5856
5993
  args: [MatDialogRef]
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "9.0.4-rc.2",
3
+ "version": "9.0.4-rc.20",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/dynamic-form": "^9.0.4-rc.2",
9
- "@praxisui/table": "^9.0.4-rc.2",
10
- "@praxisui/core": "^9.0.4-rc.2",
11
- "@praxisui/dynamic-fields": "^9.0.4-rc.2",
12
- "@praxisui/settings-panel": "^9.0.4-rc.2",
8
+ "@praxisui/dynamic-form": "^9.0.4-rc.20",
9
+ "@praxisui/table": "^9.0.4-rc.20",
10
+ "@praxisui/core": "^9.0.4-rc.20",
11
+ "@praxisui/dynamic-fields": "^9.0.4-rc.20",
12
+ "@praxisui/settings-panel": "^9.0.4-rc.20",
13
13
  "@angular/cdk": "^21.0.0",
14
14
  "@angular/forms": "^21.0.0",
15
15
  "@angular/material": "^21.0.0",
16
16
  "@angular/router": "^21.0.0",
17
- "@praxisui/ai": "^9.0.4-rc.2",
17
+ "@praxisui/ai": "^9.0.4-rc.20",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -1,8 +1,8 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, Provider, AfterViewInit, OnDestroy } from '@angular/core';
1
3
  import * as _praxisui_core from '@praxisui/core';
2
4
  import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, DynamicFormLayoutPolicy, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, MaterializedResourceIdentity, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
3
5
  export { BackConfig } from '@praxisui/core';
4
- import * as _angular_core from '@angular/core';
5
- import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, Provider, AfterViewInit, OnDestroy } from '@angular/core';
6
6
  import { MatDialogConfig, MatDialogRef, MatDialog } from '@angular/material/dialog';
7
7
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
8
8
  import { ComponentType } from '@angular/cdk/portal';
@@ -199,6 +199,7 @@ declare class PraxisCrudComponent implements OnChanges {
199
199
  tableQueryContext: Record<string, unknown> | null;
200
200
  tableFilterCriteria: Record<string, unknown>;
201
201
  tableCrudContext?: CrudContextValue;
202
+ readonly openingActionLabel: _angular_core.WritableSignal<string | null>;
202
203
  private readonly launcher;
203
204
  private readonly http;
204
205
  private readonly destroyRef;
@@ -244,6 +245,8 @@ declare class PraxisCrudComponent implements OnChanges {
244
245
  onToolbarAction(event: CrudActionRuntimeEvent): Promise<void>;
245
246
  ngOnChanges(changes: SimpleChanges): void;
246
247
  onAction(action: string, row?: Record<string, unknown>, runtimeEvent?: CrudActionRuntimeEvent): Promise<void>;
248
+ getOpeningActionStatus(actionLabel: string): string;
249
+ private resolveOpeningActionLabel;
247
250
  private hasExplicitOpenBinding;
248
251
  getCurrentTableConfigSnapshot(): TableConfig | null;
249
252
  onTableMetadataChange(): void;
@@ -294,6 +297,7 @@ declare class PraxisCrudComponent implements OnChanges {
294
297
  private resolveRowResourceId;
295
298
  private mapSurfacePresentationToCrudMode;
296
299
  private handleDiscoveredSurfaceResult;
300
+ private bindDiscoveredSurfaceLifecycle;
297
301
  private buildEffectiveTableConfig;
298
302
  private isAddLike;
299
303
  private normalizeCrudActionName;
@@ -343,6 +347,7 @@ declare class CrudLauncherService {
343
347
  private readonly storage;
344
348
  private readonly global;
345
349
  private readonly operationResolver;
350
+ private dialogHostPromise?;
346
351
  private readonly drawerAdapter;
347
352
  launch(action: CrudAction, row: Record<string, unknown> | undefined, metadata: CrudMetadata, componentKeyId?: string | null, drawerCallbacks?: {
348
353
  onClose?: () => void;
@@ -356,6 +361,8 @@ declare class CrudLauncherService {
356
361
  ref?: DialogRef<any>;
357
362
  }>;
358
363
  resolveOpenMode(action: CrudAction, metadata: CrudMetadata): FormOpenMode;
364
+ private isLikelyOverlayAction;
365
+ private loadDialogHost;
359
366
  private buildRoute;
360
367
  private mapInputs;
361
368
  private resolveActionForLaunch;
@@ -421,6 +428,8 @@ declare class DynamicFormDialogHostComponent implements OnInit {
421
428
  private extractInitialValue;
422
429
  private resolveFormConfig;
423
430
  get dialogTitle(): string;
431
+ get dialogSubtitle(): string | undefined;
432
+ get dialogIcon(): string | undefined;
424
433
  private resolveActionDialogTitle;
425
434
  private resolveResourceTitle;
426
435
  private resolveFormActions;