@praxisui/tabs 9.0.0-beta.15 → 9.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,8 @@ export class TabsHostComponent {
73
73
 
74
74
  Controlled `selectedIndex` updates the active tab without re-emitting `selectedIndexChange`, which prevents loops in dynamic-page state projections.
75
75
 
76
+ For navigation where the host renders the active content externally, set `config.group.renderBody = false` in group mode or `config.nav.renderBody = false` in nav mode. The component still owns the Material tab header/link header, selected index, keyboard navigation and selection ARIA, but it does not render internal tab/nav bodies or empty states. Hosts should render the external active panel next to the tabs and give that panel a stable accessible label that matches the active section.
77
+
76
78
  ## Metadata Shape
77
79
 
78
80
  `TabsMetadata` supports two primary modes:
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-06-24T11:17:27.420Z",
3
+ "generatedAt": "2026-06-24T15:54:00.036Z",
4
4
  "packageName": "@praxisui/tabs",
5
- "packageVersion": "9.0.0-beta.15",
5
+ "packageVersion": "9.0.0-beta.17",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 1,
@@ -1039,6 +1039,11 @@
1039
1039
  "kind": "layout",
1040
1040
  "resolver": "tabs-layout-config",
1041
1041
  "description": "Group/nav mode, header position, density, stretch and behavior settings."
1042
+ },
1043
+ {
1044
+ "kind": "renderBody",
1045
+ "resolver": "tabs-render-body-config",
1046
+ "description": "Config paths group.renderBody and nav.renderBody that let the host own external body content."
1042
1047
  }
1043
1048
  ],
1044
1049
  "operations": [
@@ -2022,9 +2027,9 @@
2022
2027
  {
2023
2028
  "chunkIndex": 2,
2024
2029
  "chunkKind": "authoring_manifest",
2025
- "content": "{\n \"schemaVersion\": \"1.0.0\",\n \"componentId\": \"praxis-tabs\",\n \"ownerPackage\": \"@praxisui/tabs\",\n \"configSchemaId\": \"TabsMetadata\",\n \"manifestVersion\": \"1.0.0\",\n \"runtimeInputs\": [\n {\n \"name\": \"config\",\n \"type\": \"TabsMetadata\",\n \"description\": \"Canonical tabs/nav configuration.\"\n },\n {\n \"name\": \"tabsId\",\n \"type\": \"string\",\n \"description\": \"Stable id used to derive persistence scope.\"\n },\n {\n \"name\": \"componentInstanceId\",\n \"type\": \"string\",\n \"description\": \"Optional instance discriminator for persistence scope.\"\n },\n {\n \"name\": \"configPersistenceStrategy\",\n \"type\": \"\\\"storage-first\\\" | \\\"input-first\\\"\",\n \"description\": \"Controls whether persisted customization or explicit input config governs the runtime. Governed previews with nested widgets should use input-first.\"\n },\n {\n \"name\": \"form\",\n \"type\": \"FormGroup\",\n \"description\": \"FormGroup consumed by dynamic field content.\"\n },\n {\n \"name\": \"context\",\n \"type\": \"Record<string, any>\",\n \"description\": \"Context passed to nested widgets.\"\n },\n {\n \"name\": \"enableCustomization\",\n \"type\": \"boolean\",\n \"description\": \"Enables Settings Panel authoring surfaces.\"\n }\n ],\n \"editableTargets\": [\n {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"A group-mode tab in config.tabs[].\"\n },\n {\n \"kind\": \"tabLabel\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"The text label of a group-mode tab.\"\n },\n {\n \"kind\": \"tabIcon\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"Icon metadata rendered in a group tab label.\"\n },\n {\n \"kind\": \"tabContent\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Dynamic fields or widgets hosted by a tab or nav link.\"\n },\n {\n \"kind\": \"activeTab\",\n \"resolver\": \"tab-index-or-id\",\n \"description\": \"Selected tab or nav link index.\"\n },\n {\n \"kind\": \"visibility\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Runtime visibility flag for a group tab or nav link.\"\n },\n {\n \"kind\": \"disabledState\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Disabled state of a tab or nav link.\"\n },\n {\n \"kind\": \"layout\",\n \"resolver\": \"tabs-layout-config\",\n \"description\": \"Group/nav mode, header position, density, stretch and behavior settings.\"\n }\n ],\n \"operations\": [\n {\n \"operationId\": \"tab.add\",\n \"title\": \"Add tab\",\n \"scope\": \"global\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tabs-array\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": false\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"textLabel\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"textLabel\": {\n \"type\": \"string\"\n },\n \"icon\": {\n \"type\": \"string\"\n },\n \"disabled\": {\n \"type\": \"boolean\"\n },\n \"visible\": {\n \"type\": \"boolean\",\n \"default\": true\n },\n \"content\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"widgets\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"append-unique\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"validators\": [\n \"tab-id-unique\",\n \"tabs-mode-compatible\",\n \"tab-content-valid\"\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"affectedPaths\": [\n \"tabs[]\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\"\n ]\n },\n {\n \"operationId\": \"tab.remove\",\n \"title\": \"Remove tab\",\n \"scope\": \"layout\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"replacementActiveTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.remove-tab-and-reselect\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"replacementActiveTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-missing\",\n \"replacement-tab-missing\",\n \"confirmation-missing\"\n ],\n \"description\": \"Removes the target tab by stable id and reselects a safe replacement when the active/default tab is removed.\"\n }\n }\n ],\n \"destructive\": true,\n \"requiresConfirmation\": true,\n \"validators\": [\n \"tab-exists\",\n \"active-tab-removal-safe\",\n \"tab-content-removal-confirmed\"\n ],\n \"affectedPaths\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\",\n \"confirmation-collected\"\n ]\n },\n {\n \"operationId\": \"tab.label.set\",\n \"title\": \"Set tab label\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabLabel\",\n \"target\": {\n \"kind\": \"tabLabel\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"textLabel\"\n ],\n \"properties\": {\n \"textLabel\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-by-key\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-label-valid\"\n ],\n \"affectedPaths\": [\n \"tabs[].textLabel\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.icon.set\",\n \"title\": \"Set tab icon\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabIcon\",\n \"target\": {\n \"kind\": \"tabIcon\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"icon\"\n ],\n \"properties\": {\n \"icon\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-by-key\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-icon-valid\"\n ],\n \"affectedPaths\": [\n \"tabs[].icon\"\n ],\n \"submissionImpact\": \"visual-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.order.set\",\n \"title\": \"Reorder tabs\",\n \"scope\": \"layout\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"beforeTabId\"\n ],\n \"properties\": {\n \"beforeTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.reorder-tab-and-preserve-selection\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"beforeTabId\"\n ],\n \"properties\": {\n \"beforeTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-missing\",\n \"before-tab-missing\",\n \"unstable-tab-id\"\n ],\n \"description\": \"Reorders tabs by stable id and remaps group.selectedIndex when the selected tab crosses positions.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-order-deterministic\"\n ],\n \"affectedPaths\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.disabled.set\",\n \"title\": \"Set tab disabled state\",\n \"scope\": \"interaction\",\n \"targetKind\": \"disabledState\",\n \"target\": {\n \"kind\": \"disabledState\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"disabled\"\n ],\n \"properties\": {\n \"disabled\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-disabled\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[].disabled\",\n \"nav.links[].disabled\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"disabled\"\n ],\n \"properties\": {\n \"disabled\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"ambiguous-target\",\n \"active-item-disabled-without-reselection\"\n ],\n \"description\": \"Sets disabled on the resolved group tab or nav link without guessing between modes.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"active-tab-disabled-safe\"\n ],\n \"affectedPaths\": [\n \"tabs[].disabled\",\n \"nav.links[].disabled\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"tab.visible.set\",\n \"title\": \"Set tab visibility\",\n \"scope\": \"interaction\",\n \"targetKind\": \"visibility\",\n \"target\": {\n \"kind\": \"visibility\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"visible\"\n ],\n \"properties\": {\n \"visible\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-visible\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[].visible\",\n \"nav.links[].visible\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"visible\"\n ],\n \"properties\": {\n \"visible\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"ambiguous-target\",\n \"active-item-hidden-without-reselection\"\n ],\n \"description\": \"Sets visible on the resolved group tab or nav link and preserves deterministic visible-index mapping.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"active-tab-visibility-safe\"\n ],\n \"affectedPaths\": [\n \"tabs[].visible\",\n \"nav.links[].visible\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"tab.active.set\",\n \"title\": \"Set active tab\",\n \"scope\": \"interaction\",\n \"targetKind\": \"activeTab\",\n \"target\": {\n \"kind\": \"activeTab\",\n \"resolver\": \"tab-index-or-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"selectedIndex\"\n ],\n \"properties\": {\n \"selectedIndex\": {\n \"type\": \"number\"\n },\n \"tabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-active-item\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"selectedIndex\"\n ],\n \"properties\": {\n \"selectedIndex\": {\n \"type\": \"number\"\n },\n \"tabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"selected-index-out-of-range\",\n \"hidden-or-disabled-target\"\n ],\n \"description\": \"Sets the active index for the current primary mode using either selectedIndex or a resolved tab/link id.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"active-tab-exists\",\n \"selected-index-in-range\"\n ],\n \"affectedPaths\": [\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"layout.variant.set\",\n \"title\": \"Set tabs layout variant\",\n \"scope\": \"layout\",\n \"targetKind\": \"layout\",\n \"target\": {\n \"kind\": \"layout\",\n \"resolver\": \"tabs-layout-config\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"mode\"\n ],\n \"properties\": {\n \"mode\": {\n \"enum\": [\n \"group\",\n \"nav\"\n ]\n },\n \"density\": {\n \"enum\": [\n \"compact\",\n \"comfortable\",\n \"spacious\"\n ]\n },\n \"headerPosition\": {\n \"enum\": [\n \"above\",\n \"below\"\n ]\n },\n \"alignTabs\": {\n \"enum\": [\n \"start\",\n \"center\",\n \"end\"\n ]\n },\n \"stretchTabs\": {\n \"type\": \"boolean\"\n },\n \"lazyLoad\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-object\",\n \"path\": \"appearance\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"group\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"nav\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"behavior\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tabs-mode-compatible\",\n \"layout-values-valid\",\n \"editor-runtime-round-trip\"\n ],\n \"affectedPaths\": [\n \"appearance.density\",\n \"group.headerPosition\",\n \"group.alignTabs\",\n \"group.stretchTabs\",\n \"nav.stretchTabs\",\n \"behavior.lazyLoad\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\"\n ]\n },\n {\n \"operationId\": \"tab.content.set\",\n \"title\": \"Set tab content\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabContent\",\n \"target\": {\n \"kind\": \"tabContent\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"textLabel\": {\n \"type\": \"string\"\n },\n \"icon\": {\n \"type\": \"string\"\n },\n \"disabled\": {\n \"type\": \"boolean\"\n },\n \"visible\": {\n \"type\": \"boolean\"\n },\n \"content\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"widgets\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-content\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\"\n ],\n \"writes\": [\n \"tabs[].content\",\n \"tabs[].widgets\",\n \"nav.links[].content\",\n \"nav.links[].widgets\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"invalid-dynamic-field-content\",\n \"invalid-widget-definition\"\n ],\n \"description\": \"Updates content/widgets only on the resolved group tab or nav link while preserving nested widget identity.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"tab-content-valid\",\n \"widget-event-delegated\"\n ],\n \"affectedPaths\": [\n \"tabs[].content\",\n \"tabs[].widgets\",\n \"nav.links[].content\",\n \"nav.links[].widgets\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n }\n ],\n \"validators\": [\n {\n \"validatorId\": \"tab-id-unique\",\n \"level\": \"error\",\n \"code\": \"PTABS001\",\n \"description\": \"Tab ids and nav link ids must be unique within their mode.\"\n },\n {\n \"validatorId\": \"tab-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS002\",\n \"description\": \"Target tab must exist before applying the operation.\"\n },\n {\n \"validatorId\": \"tab-or-link-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS003\",\n \"description\": \"Target must resolve to an existing group tab or nav link.\"\n },\n {\n \"validatorId\": \"active-tab-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS004\",\n \"description\": \"Active tab or nav link selection must reference an existing item.\"\n },\n {\n \"validatorId\": \"selected-index-in-range\",\n \"level\": \"error\",\n \"code\": \"PTABS005\",\n \"description\": \"Selected index must be clamped to the target mode item count.\"\n },\n {\n \"validatorId\": \"active-tab-removal-safe\",\n \"level\": \"error\",\n \"code\": \"PTABS006\",\n \"description\": \"Removing the active/default tab requires confirmation or a replacement active tab.\"\n },\n {\n \"validatorId\": \"tab-content-removal-confirmed\",\n \"level\": \"error\",\n \"code\": \"PTABS007\",\n \"description\": \"Removing a tab or link with content/widgets is destructive and requires confirmation.\"\n },\n {\n \"validatorId\": \"tab-label-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS008\",\n \"description\": \"Tab labels must be non-empty text values after localization/domain projection.\"\n },\n {\n \"validatorId\": \"tab-icon-valid\",\n \"level\": \"warning\",\n \"code\": \"PTABS009\",\n \"description\": \"Tab icon metadata must remain compatible with the icon directive and editor round-trip.\"\n },\n {\n \"validatorId\": \"tab-order-deterministic\",\n \"level\": \"error\",\n \"code\": \"PTABS010\",\n \"description\": \"Tab ordering must use stable ids, not transient array index as identity.\"\n },\n {\n \"validatorId\": \"tabs-mode-compatible\",\n \"level\": \"error\",\n \"code\": \"PTABS011\",\n \"description\": \"Authoring must resolve to one primary mode: group tabs or nav links.\"\n },\n {\n \"validatorId\": \"layout-values-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS012\",\n \"description\": \"Layout values must match TabsMetadata enums and runtime bindings.\"\n },\n {\n \"validatorId\": \"editor-runtime-round-trip\",\n \"level\": \"error\",\n \"code\": \"PTABS013\",\n \"description\": \"Settings Panel, quick setup, JSON editor and runtime must preserve ids, order and selected index.\"\n },\n {\n \"validatorId\": \"active-tab-disabled-safe\",\n \"level\": \"warning\",\n \"code\": \"PTABS014\",\n \"description\": \"Disabling the active item should move selection or request explicit confirmation.\"\n },\n {\n \"validatorId\": \"active-tab-visibility-safe\",\n \"level\": \"warning\",\n \"code\": \"PTABS015\",\n \"description\": \"Hiding the active item should move selection or request explicit confirmation.\"\n },\n {\n \"validatorId\": \"tab-content-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS016\",\n \"description\": \"Tab content must be valid DynamicFieldMetadata[] or WidgetDefinition[] and preserve nested widget identity.\"\n },\n {\n \"validatorId\": \"widget-event-delegated\",\n \"level\": \"info\",\n \"code\": \"PTABS017\",\n \"description\": \"Nested widget event paths remain delegated to the tabs runtime contract and are not redefined by authoring.\"\n }\n ],\n \"roundTripRequirements\": [\n \"Operations must preserve stable tab/link ids; array index may be used only as a resolver fallback, never as canonical identity.\",\n \"Settings Panel, quick setup and JSON editor must round-trip through TabsAuthoringDocument without losing config or bindings.\",\n \"Group and nav modes must remain mutually explicit; authoring cannot silently mix config.tabs and nav.links as competing primary modes.\",\n \"Nested widget events remain delegated through widgetEvent path enrichment and component-port nestedPath semantics.\"\n ],\n \"examples\": [\n {\n \"id\": \"add-overview-tab\",\n \"request\": \"Add an Overview tab before the details tab.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"overview\",\n \"textLabel\": \"Overview\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"add-list-to-current-tab\",\n \"request\": \"Create a list widget inside the current training tab.\",\n \"operationId\": \"tab.content.set\",\n \"target\": \"training\",\n \"params\": {\n \"widgets\": [\n {\n \"id\": \"training-list\",\n \"component\": \"praxis-list\",\n \"title\": \"Training list\"\n }\n ]\n },\n \"isPositive\": true\n },\n {\n \"id\": \"add-form-fields-to-existing-tab\",\n \"request\": \"Add name, date and status fields to the existing onboarding tab.\",\n \"operationId\": \"tab.content.set\",\n \"target\": \"onboarding\",\n \"params\": {\n \"content\": [\n {\n \"name\": \"name\",\n \"label\": \"Name\",\n \"controlType\": \"text\"\n },\n {\n \"name\": \"plannedDate\",\n \"label\": \"Planned date\",\n \"controlType\": \"date\"\n },\n {\n \"name\": \"status\",\n \"label\": \"Status\",\n \"controlType\": \"select\"\n }\n ]\n },\n \"isPositive\": true\n },\n {\n \"id\": \"rename-tab\",\n \"request\": \"Rename the details tab to Account Details.\",\n \"operationId\": \"tab.label.set\",\n \"target\": \"details\",\n \"params\": {\n \"textLabel\": \"Account Details\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"reorder-tabs\",\n \"request\": \"Move billing before overview.\",\n \"operationId\": \"tab.order.set\",\n \"target\": \"billing\",\n \"params\": {\n \"beforeTabId\": \"overview\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"disable-tab\",\n \"request\": \"Disable the audit tab until the user has permission.\",\n \"operationId\": \"tab.disabled.set\",\n \"target\": \"audit\",\n \"params\": {\n \"disabled\": true\n },\n \"isPositive\": true\n },\n {\n \"id\": \"activate-tab\",\n \"request\": \"Open the documents tab by default.\",\n \"operationId\": \"tab.active.set\",\n \"target\": \"documents\",\n \"params\": {\n \"tabId\": \"documents\",\n \"selectedIndex\": 2\n },\n \"isPositive\": true\n },\n {\n \"id\": \"reject-duplicate-tab-id\",\n \"request\": \"Add another tab with id overview.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"overview\",\n \"textLabel\": \"Duplicate Overview\"\n },\n \"isPositive\": false\n },\n {\n \"id\": \"reject-current-tab-content-as-tab-add\",\n \"request\": \"Create a list in this tab; do not add a new tab.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"list-in-this-tab\",\n \"textLabel\": \"List in this tab\"\n },\n \"isPositive\": false\n },\n {\n \"id\": \"confirm-remove-content-tab\",\n \"request\": \"Remove the details tab that contains widgets.\",\n \"operationId\": \"tab.remove\",\n \"target\": \"details\",\n \"params\": {\n \"replacementActiveTabId\": \"overview\"\n },\n \"isPositive\": true\n }\n ]\n}",
2030
+ "content": "{\n \"schemaVersion\": \"1.0.0\",\n \"componentId\": \"praxis-tabs\",\n \"ownerPackage\": \"@praxisui/tabs\",\n \"configSchemaId\": \"TabsMetadata\",\n \"manifestVersion\": \"1.0.0\",\n \"runtimeInputs\": [\n {\n \"name\": \"config\",\n \"type\": \"TabsMetadata\",\n \"description\": \"Canonical tabs/nav configuration.\"\n },\n {\n \"name\": \"tabsId\",\n \"type\": \"string\",\n \"description\": \"Stable id used to derive persistence scope.\"\n },\n {\n \"name\": \"componentInstanceId\",\n \"type\": \"string\",\n \"description\": \"Optional instance discriminator for persistence scope.\"\n },\n {\n \"name\": \"configPersistenceStrategy\",\n \"type\": \"\\\"storage-first\\\" | \\\"input-first\\\"\",\n \"description\": \"Controls whether persisted customization or explicit input config governs the runtime. Governed previews with nested widgets should use input-first.\"\n },\n {\n \"name\": \"form\",\n \"type\": \"FormGroup\",\n \"description\": \"FormGroup consumed by dynamic field content.\"\n },\n {\n \"name\": \"context\",\n \"type\": \"Record<string, any>\",\n \"description\": \"Context passed to nested widgets.\"\n },\n {\n \"name\": \"enableCustomization\",\n \"type\": \"boolean\",\n \"description\": \"Enables Settings Panel authoring surfaces.\"\n }\n ],\n \"editableTargets\": [\n {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"A group-mode tab in config.tabs[].\"\n },\n {\n \"kind\": \"tabLabel\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"The text label of a group-mode tab.\"\n },\n {\n \"kind\": \"tabIcon\",\n \"resolver\": \"tab-by-id-or-label\",\n \"description\": \"Icon metadata rendered in a group tab label.\"\n },\n {\n \"kind\": \"tabContent\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Dynamic fields or widgets hosted by a tab or nav link.\"\n },\n {\n \"kind\": \"activeTab\",\n \"resolver\": \"tab-index-or-id\",\n \"description\": \"Selected tab or nav link index.\"\n },\n {\n \"kind\": \"visibility\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Runtime visibility flag for a group tab or nav link.\"\n },\n {\n \"kind\": \"disabledState\",\n \"resolver\": \"tab-or-link-by-id\",\n \"description\": \"Disabled state of a tab or nav link.\"\n },\n {\n \"kind\": \"layout\",\n \"resolver\": \"tabs-layout-config\",\n \"description\": \"Group/nav mode, header position, density, stretch and behavior settings.\"\n },\n {\n \"kind\": \"renderBody\",\n \"resolver\": \"tabs-render-body-config\",\n \"description\": \"Config paths group.renderBody and nav.renderBody that let the host own external body content.\"\n }\n ],\n \"operations\": [\n {\n \"operationId\": \"tab.add\",\n \"title\": \"Add tab\",\n \"scope\": \"global\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tabs-array\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": false\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"textLabel\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"textLabel\": {\n \"type\": \"string\"\n },\n \"icon\": {\n \"type\": \"string\"\n },\n \"disabled\": {\n \"type\": \"boolean\"\n },\n \"visible\": {\n \"type\": \"boolean\",\n \"default\": true\n },\n \"content\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"widgets\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"append-unique\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"validators\": [\n \"tab-id-unique\",\n \"tabs-mode-compatible\",\n \"tab-content-valid\"\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"affectedPaths\": [\n \"tabs[]\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\"\n ]\n },\n {\n \"operationId\": \"tab.remove\",\n \"title\": \"Remove tab\",\n \"scope\": \"layout\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"replacementActiveTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.remove-tab-and-reselect\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"replacementActiveTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-missing\",\n \"replacement-tab-missing\",\n \"confirmation-missing\"\n ],\n \"description\": \"Removes the target tab by stable id and reselects a safe replacement when the active/default tab is removed.\"\n }\n }\n ],\n \"destructive\": true,\n \"requiresConfirmation\": true,\n \"validators\": [\n \"tab-exists\",\n \"active-tab-removal-safe\",\n \"tab-content-removal-confirmed\"\n ],\n \"affectedPaths\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\",\n \"confirmation-collected\"\n ]\n },\n {\n \"operationId\": \"tab.label.set\",\n \"title\": \"Set tab label\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabLabel\",\n \"target\": {\n \"kind\": \"tabLabel\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"textLabel\"\n ],\n \"properties\": {\n \"textLabel\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-by-key\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-label-valid\"\n ],\n \"affectedPaths\": [\n \"tabs[].textLabel\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.icon.set\",\n \"title\": \"Set tab icon\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabIcon\",\n \"target\": {\n \"kind\": \"tabIcon\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"icon\"\n ],\n \"properties\": {\n \"icon\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-by-key\",\n \"path\": \"tabs[]\",\n \"key\": \"id\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-icon-valid\"\n ],\n \"affectedPaths\": [\n \"tabs[].icon\"\n ],\n \"submissionImpact\": \"visual-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.order.set\",\n \"title\": \"Reorder tabs\",\n \"scope\": \"layout\",\n \"targetKind\": \"tab\",\n \"target\": {\n \"kind\": \"tab\",\n \"resolver\": \"tab-by-id-or-label\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"beforeTabId\"\n ],\n \"properties\": {\n \"beforeTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.reorder-tab-and-preserve-selection\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"beforeTabId\"\n ],\n \"properties\": {\n \"beforeTabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-missing\",\n \"before-tab-missing\",\n \"unstable-tab-id\"\n ],\n \"description\": \"Reorders tabs by stable id and remaps group.selectedIndex when the selected tab crosses positions.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-exists\",\n \"tab-order-deterministic\"\n ],\n \"affectedPaths\": [\n \"tabs[]\",\n \"group.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-exists\"\n ]\n },\n {\n \"operationId\": \"tab.disabled.set\",\n \"title\": \"Set tab disabled state\",\n \"scope\": \"interaction\",\n \"targetKind\": \"disabledState\",\n \"target\": {\n \"kind\": \"disabledState\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"disabled\"\n ],\n \"properties\": {\n \"disabled\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-disabled\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[].disabled\",\n \"nav.links[].disabled\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"disabled\"\n ],\n \"properties\": {\n \"disabled\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"ambiguous-target\",\n \"active-item-disabled-without-reselection\"\n ],\n \"description\": \"Sets disabled on the resolved group tab or nav link without guessing between modes.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"active-tab-disabled-safe\"\n ],\n \"affectedPaths\": [\n \"tabs[].disabled\",\n \"nav.links[].disabled\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"tab.visible.set\",\n \"title\": \"Set tab visibility\",\n \"scope\": \"interaction\",\n \"targetKind\": \"visibility\",\n \"target\": {\n \"kind\": \"visibility\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"visible\"\n ],\n \"properties\": {\n \"visible\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-visible\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"tabs[].visible\",\n \"nav.links[].visible\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"visible\"\n ],\n \"properties\": {\n \"visible\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"ambiguous-target\",\n \"active-item-hidden-without-reselection\"\n ],\n \"description\": \"Sets visible on the resolved group tab or nav link and preserves deterministic visible-index mapping.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"active-tab-visibility-safe\"\n ],\n \"affectedPaths\": [\n \"tabs[].visible\",\n \"nav.links[].visible\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"tab.active.set\",\n \"title\": \"Set active tab\",\n \"scope\": \"interaction\",\n \"targetKind\": \"activeTab\",\n \"target\": {\n \"kind\": \"activeTab\",\n \"resolver\": \"tab-index-or-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"selectedIndex\"\n ],\n \"properties\": {\n \"selectedIndex\": {\n \"type\": \"number\"\n },\n \"tabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-active-item\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\",\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"writes\": [\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"selectedIndex\"\n ],\n \"properties\": {\n \"selectedIndex\": {\n \"type\": \"number\"\n },\n \"tabId\": {\n \"type\": \"string\"\n }\n }\n },\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"selected-index-out-of-range\",\n \"hidden-or-disabled-target\"\n ],\n \"description\": \"Sets the active index for the current primary mode using either selectedIndex or a resolved tab/link id.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"active-tab-exists\",\n \"selected-index-in-range\"\n ],\n \"affectedPaths\": [\n \"group.selectedIndex\",\n \"nav.selectedIndex\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n },\n {\n \"operationId\": \"layout.variant.set\",\n \"title\": \"Set tabs layout variant\",\n \"scope\": \"layout\",\n \"targetKind\": \"layout\",\n \"target\": {\n \"kind\": \"layout\",\n \"resolver\": \"tabs-layout-config\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"required\": [\n \"mode\"\n ],\n \"properties\": {\n \"mode\": {\n \"enum\": [\n \"group\",\n \"nav\"\n ]\n },\n \"density\": {\n \"enum\": [\n \"compact\",\n \"comfortable\",\n \"spacious\"\n ]\n },\n \"headerPosition\": {\n \"enum\": [\n \"above\",\n \"below\"\n ]\n },\n \"alignTabs\": {\n \"enum\": [\n \"start\",\n \"center\",\n \"end\"\n ]\n },\n \"stretchTabs\": {\n \"type\": \"boolean\"\n },\n \"lazyLoad\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"merge-object\",\n \"path\": \"appearance\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"group\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"nav\"\n },\n {\n \"kind\": \"merge-object\",\n \"path\": \"behavior\"\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tabs-mode-compatible\",\n \"layout-values-valid\",\n \"editor-runtime-round-trip\"\n ],\n \"affectedPaths\": [\n \"appearance.density\",\n \"group.headerPosition\",\n \"group.alignTabs\",\n \"group.stretchTabs\",\n \"nav.stretchTabs\",\n \"behavior.lazyLoad\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\"\n ]\n },\n {\n \"operationId\": \"tab.content.set\",\n \"title\": \"Set tab content\",\n \"scope\": \"layout\",\n \"targetKind\": \"tabContent\",\n \"target\": {\n \"kind\": \"tabContent\",\n \"resolver\": \"tab-or-link-by-id\",\n \"ambiguityPolicy\": \"fail\",\n \"required\": true\n },\n \"inputSchema\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"textLabel\": {\n \"type\": \"string\"\n },\n \"icon\": {\n \"type\": \"string\"\n },\n \"disabled\": {\n \"type\": \"boolean\"\n },\n \"visible\": {\n \"type\": \"boolean\"\n },\n \"content\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"widgets\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n }\n }\n },\n \"effects\": [\n {\n \"kind\": \"compile-domain-patch\",\n \"handler\": \"tabs.set-tab-or-link-content\",\n \"handlerContract\": {\n \"reads\": [\n \"tabs[]\",\n \"nav.links[]\"\n ],\n \"writes\": [\n \"tabs[].content\",\n \"tabs[].widgets\",\n \"nav.links[].content\",\n \"nav.links[].widgets\"\n ],\n \"identityKeys\": [\n \"tabs[].id\",\n \"nav.links[].id\"\n ],\n \"failureModes\": [\n \"target-tab-or-link-missing\",\n \"invalid-dynamic-field-content\",\n \"invalid-widget-definition\"\n ],\n \"description\": \"Updates content/widgets only on the resolved group tab or nav link while preserving nested widget identity.\"\n }\n }\n ],\n \"destructive\": false,\n \"requiresConfirmation\": false,\n \"validators\": [\n \"tab-or-link-exists\",\n \"tab-content-valid\",\n \"widget-event-delegated\"\n ],\n \"affectedPaths\": [\n \"tabs[].content\",\n \"tabs[].widgets\",\n \"nav.links[].content\",\n \"nav.links[].widgets\"\n ],\n \"submissionImpact\": \"config-only\",\n \"preconditions\": [\n \"config-initialized\",\n \"target-tab-or-link-exists\"\n ]\n }\n ],\n \"validators\": [\n {\n \"validatorId\": \"tab-id-unique\",\n \"level\": \"error\",\n \"code\": \"PTABS001\",\n \"description\": \"Tab ids and nav link ids must be unique within their mode.\"\n },\n {\n \"validatorId\": \"tab-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS002\",\n \"description\": \"Target tab must exist before applying the operation.\"\n },\n {\n \"validatorId\": \"tab-or-link-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS003\",\n \"description\": \"Target must resolve to an existing group tab or nav link.\"\n },\n {\n \"validatorId\": \"active-tab-exists\",\n \"level\": \"error\",\n \"code\": \"PTABS004\",\n \"description\": \"Active tab or nav link selection must reference an existing item.\"\n },\n {\n \"validatorId\": \"selected-index-in-range\",\n \"level\": \"error\",\n \"code\": \"PTABS005\",\n \"description\": \"Selected index must be clamped to the target mode item count.\"\n },\n {\n \"validatorId\": \"active-tab-removal-safe\",\n \"level\": \"error\",\n \"code\": \"PTABS006\",\n \"description\": \"Removing the active/default tab requires confirmation or a replacement active tab.\"\n },\n {\n \"validatorId\": \"tab-content-removal-confirmed\",\n \"level\": \"error\",\n \"code\": \"PTABS007\",\n \"description\": \"Removing a tab or link with content/widgets is destructive and requires confirmation.\"\n },\n {\n \"validatorId\": \"tab-label-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS008\",\n \"description\": \"Tab labels must be non-empty text values after localization/domain projection.\"\n },\n {\n \"validatorId\": \"tab-icon-valid\",\n \"level\": \"warning\",\n \"code\": \"PTABS009\",\n \"description\": \"Tab icon metadata must remain compatible with the icon directive and editor round-trip.\"\n },\n {\n \"validatorId\": \"tab-order-deterministic\",\n \"level\": \"error\",\n \"code\": \"PTABS010\",\n \"description\": \"Tab ordering must use stable ids, not transient array index as identity.\"\n },\n {\n \"validatorId\": \"tabs-mode-compatible\",\n \"level\": \"error\",\n \"code\": \"PTABS011\",\n \"description\": \"Authoring must resolve to one primary mode: group tabs or nav links.\"\n },\n {\n \"validatorId\": \"layout-values-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS012\",\n \"description\": \"Layout values must match TabsMetadata enums and runtime bindings.\"\n },\n {\n \"validatorId\": \"editor-runtime-round-trip\",\n \"level\": \"error\",\n \"code\": \"PTABS013\",\n \"description\": \"Settings Panel, quick setup, JSON editor and runtime must preserve ids, order and selected index.\"\n },\n {\n \"validatorId\": \"active-tab-disabled-safe\",\n \"level\": \"warning\",\n \"code\": \"PTABS014\",\n \"description\": \"Disabling the active item should move selection or request explicit confirmation.\"\n },\n {\n \"validatorId\": \"active-tab-visibility-safe\",\n \"level\": \"warning\",\n \"code\": \"PTABS015\",\n \"description\": \"Hiding the active item should move selection or request explicit confirmation.\"\n },\n {\n \"validatorId\": \"tab-content-valid\",\n \"level\": \"error\",\n \"code\": \"PTABS016\",\n \"description\": \"Tab content must be valid DynamicFieldMetadata[] or WidgetDefinition[] and preserve nested widget identity.\"\n },\n {\n \"validatorId\": \"widget-event-delegated\",\n \"level\": \"info\",\n \"code\": \"PTABS017\",\n \"description\": \"Nested widget event paths remain delegated to the tabs runtime contract and are not redefined by authoring.\"\n }\n ],\n \"roundTripRequirements\": [\n \"Operations must preserve stable tab/link ids; array index may be used only as a resolver fallback, never as canonical identity.\",\n \"Settings Panel, quick setup and JSON editor must round-trip through TabsAuthoringDocument without losing config or bindings.\",\n \"Group and nav modes must remain mutually explicit; authoring cannot silently mix config.tabs and nav.links as competing primary modes.\",\n \"Nested widget events remain delegated through widgetEvent path enrichment and component-port nestedPath semantics.\"\n ],\n \"examples\": [\n {\n \"id\": \"add-overview-tab\",\n \"request\": \"Add an Overview tab before the details tab.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"overview\",\n \"textLabel\": \"Overview\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"add-list-to-current-tab\",\n \"request\": \"Create a list widget inside the current training tab.\",\n \"operationId\": \"tab.content.set\",\n \"target\": \"training\",\n \"params\": {\n \"widgets\": [\n {\n \"id\": \"training-list\",\n \"component\": \"praxis-list\",\n \"title\": \"Training list\"\n }\n ]\n },\n \"isPositive\": true\n },\n {\n \"id\": \"add-form-fields-to-existing-tab\",\n \"request\": \"Add name, date and status fields to the existing onboarding tab.\",\n \"operationId\": \"tab.content.set\",\n \"target\": \"onboarding\",\n \"params\": {\n \"content\": [\n {\n \"name\": \"name\",\n \"label\": \"Name\",\n \"controlType\": \"text\"\n },\n {\n \"name\": \"plannedDate\",\n \"label\": \"Planned date\",\n \"controlType\": \"date\"\n },\n {\n \"name\": \"status\",\n \"label\": \"Status\",\n \"controlType\": \"select\"\n }\n ]\n },\n \"isPositive\": true\n },\n {\n \"id\": \"rename-tab\",\n \"request\": \"Rename the details tab to Account Details.\",\n \"operationId\": \"tab.label.set\",\n \"target\": \"details\",\n \"params\": {\n \"textLabel\": \"Account Details\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"reorder-tabs\",\n \"request\": \"Move billing before overview.\",\n \"operationId\": \"tab.order.set\",\n \"target\": \"billing\",\n \"params\": {\n \"beforeTabId\": \"overview\"\n },\n \"isPositive\": true\n },\n {\n \"id\": \"disable-tab\",\n \"request\": \"Disable the audit tab until the user has permission.\",\n \"operationId\": \"tab.disabled.set\",\n \"target\": \"audit\",\n \"params\": {\n \"disabled\": true\n },\n \"isPositive\": true\n },\n {\n \"id\": \"activate-tab\",\n \"request\": \"Open the documents tab by default.\",\n \"operationId\": \"tab.active.set\",\n \"target\": \"documents\",\n \"params\": {\n \"tabId\": \"documents\",\n \"selectedIndex\": 2\n },\n \"isPositive\": true\n },\n {\n \"id\": \"reject-duplicate-tab-id\",\n \"request\": \"Add another tab with id overview.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"overview\",\n \"textLabel\": \"Duplicate Overview\"\n },\n \"isPositive\": false\n },\n {\n \"id\": \"reject-current-tab-content-as-tab-add\",\n \"request\": \"Create a list in this tab; do not add a new tab.\",\n \"operationId\": \"tab.add\",\n \"params\": {\n \"id\": \"list-in-this-tab\",\n \"textLabel\": \"List in this tab\"\n },\n \"isPositive\": false\n },\n {\n \"id\": \"confirm-remove-content-tab\",\n \"request\": \"Remove the details tab that contains widgets.\",\n \"operationId\": \"tab.remove\",\n \"target\": \"details\",\n \"params\": {\n \"replacementActiveTabId\": \"overview\"\n },\n \"isPositive\": true\n }\n ]\n}",
2026
2031
  "sourcePointer": "praxis-ui-angular/projects/praxis-tabs/src/lib/ai/praxis-tabs-authoring-manifest.ts",
2027
- "contentHash": "f6ba5ad9580601e9a0f3edd06d36da420a21095928e443bba073bbf14fa9db80",
2032
+ "contentHash": "e47f82449d83a025be71346a144a6ce74884c2f28326bd415d7cd2b6133c4868",
2028
2033
  "sourceKind": "component_definition",
2029
2034
  "sourceId": "praxis-tabs",
2030
2035
  "corpusVersion": "1.0.0"
@@ -364,6 +364,7 @@ const PRAXIS_TABS_PT_BR = {
364
364
  'editor.toggles.disablePagination': 'Sem paginação',
365
365
  'editor.toggles.disableRipple': 'Sem ripple',
366
366
  'editor.toggles.preserveContent': 'Preservar conteúdo',
367
+ 'editor.toggles.renderBody': 'Renderizar conteúdo interno',
367
368
  'editor.toggles.stretchTabs': 'Esticar abas',
368
369
  'editor.toggles.highContrast': 'Alto contraste',
369
370
  'editor.toggles.reduceMotion': 'Reduzir movimento',
@@ -509,6 +510,7 @@ const PRAXIS_TABS_EN_US = {
509
510
  'editor.toggles.disablePagination': 'Disable pagination',
510
511
  'editor.toggles.disableRipple': 'Disable ripple',
511
512
  'editor.toggles.preserveContent': 'Preserve content',
513
+ 'editor.toggles.renderBody': 'Render internal content',
512
514
  'editor.toggles.stretchTabs': 'Stretch tabs',
513
515
  'editor.toggles.highContrast': 'High contrast',
514
516
  'editor.toggles.reduceMotion': 'Reduce motion',
@@ -1116,6 +1118,7 @@ class PraxisTabsConfigEditor {
1116
1118
  <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1117
1119
  <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1118
1120
  <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.preserveContent', 'Preservar conteudo') }}</mat-slide-toggle>
1121
+ <mat-slide-toggle [ngModel]="group.renderBody !== false" (ngModelChange)="group.renderBody = $event ? undefined : false; onAppearanceChange()">{{ t('editor.toggles.renderBody', 'Renderizar conteudo interno') }}</mat-slide-toggle>
1119
1122
  <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1120
1123
  </div>
1121
1124
  </div>
@@ -1197,6 +1200,7 @@ class PraxisTabsConfigEditor {
1197
1200
  <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1198
1201
  <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1199
1202
  <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1203
+ <mat-slide-toggle [ngModel]="nav.renderBody !== false" (ngModelChange)="nav.renderBody = $event ? undefined : false; onAppearanceChange()">{{ t('editor.toggles.renderBody', 'Renderizar conteudo interno') }}</mat-slide-toggle>
1200
1204
  <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1201
1205
  </div>
1202
1206
  </div>
@@ -1656,6 +1660,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1656
1660
  <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1657
1661
  <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1658
1662
  <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.preserveContent', 'Preservar conteudo') }}</mat-slide-toggle>
1663
+ <mat-slide-toggle [ngModel]="group.renderBody !== false" (ngModelChange)="group.renderBody = $event ? undefined : false; onAppearanceChange()">{{ t('editor.toggles.renderBody', 'Renderizar conteudo interno') }}</mat-slide-toggle>
1659
1664
  <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1660
1665
  </div>
1661
1666
  </div>
@@ -1737,6 +1742,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1737
1742
  <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1738
1743
  <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1739
1744
  <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1745
+ <mat-slide-toggle [ngModel]="nav.renderBody !== false" (ngModelChange)="nav.renderBody = $event ? undefined : false; onAppearanceChange()">{{ t('editor.toggles.renderBody', 'Renderizar conteudo interno') }}</mat-slide-toggle>
1740
1746
  <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1741
1747
  </div>
1742
1748
  </div>
@@ -3964,6 +3970,12 @@ class PraxisTabs {
3964
3970
  groupContentReady(index) {
3965
3971
  return !this.isLazy() || this.groupLoaded.has(index) || this.selectedIndexSignal() === index;
3966
3972
  }
3973
+ shouldRenderGroupBody() {
3974
+ return this.config?.group?.renderBody !== false;
3975
+ }
3976
+ shouldRenderNavBody() {
3977
+ return this.config?.nav?.renderBody !== false;
3978
+ }
3967
3979
  navContentReady(index) {
3968
3980
  return !this.isLazy() || this.navLoaded.has(index) || this.currentNavIndex() === index;
3969
3981
  }
@@ -4360,8 +4372,11 @@ class PraxisTabs {
4360
4372
  </a>
4361
4373
  }
4362
4374
  </nav>
4363
- <mat-tab-nav-panel #tabPanel>
4364
- @if (currentNavIndex() >= 0) {
4375
+ <mat-tab-nav-panel
4376
+ #tabPanel
4377
+ [class.praxis-tabs-nav-panel--header-only]="!shouldRenderNavBody()"
4378
+ >
4379
+ @if (shouldRenderNavBody() && currentNavIndex() >= 0) {
4365
4380
  <div class="praxis-tabnav-content">
4366
4381
  @if (config?.nav?.links?.[currentNavIndex()]; as l) {
4367
4382
  @if ((l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex())) {
@@ -4396,6 +4411,7 @@ class PraxisTabs {
4396
4411
  </mat-tab-nav-panel>
4397
4412
  } @else {
4398
4413
  <mat-tab-group
4414
+ [class.praxis-tabs-group--header-only]="!shouldRenderGroupBody()"
4399
4415
  [dynamicHeight]="config?.group?.dynamicHeight"
4400
4416
  [disableRipple]="config?.group?.disableRipple"
4401
4417
  [disablePagination]="config?.group?.disablePagination"
@@ -4467,6 +4483,7 @@ class PraxisTabs {
4467
4483
  </button>
4468
4484
  }
4469
4485
  </ng-template>
4486
+ @if (shouldRenderGroupBody()) {
4470
4487
  @if ((entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index)) {
4471
4488
  @if (entry.tab.content) {
4472
4489
  <ng-container
@@ -4493,6 +4510,7 @@ class PraxisTabs {
4493
4510
  [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4494
4511
  ></praxis-empty-state-card>
4495
4512
  }
4513
+ }
4496
4514
  </mat-tab>
4497
4515
  }
4498
4516
  </mat-tab-group>
@@ -4524,7 +4542,7 @@ class PraxisTabs {
4524
4542
  </button>
4525
4543
  }
4526
4544
  </div>
4527
- `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2_1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2_1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i9.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i9.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i9.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4545
+ `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.praxis-tabs-group--header-only .mat-mdc-tab-body-wrapper,.praxis-tabs-nav-panel--header-only{display:none}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2_1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2_1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i9.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i9.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i9.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4528
4546
  }
4529
4547
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisTabs, decorators: [{
4530
4548
  type: Component,
@@ -4669,8 +4687,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4669
4687
  </a>
4670
4688
  }
4671
4689
  </nav>
4672
- <mat-tab-nav-panel #tabPanel>
4673
- @if (currentNavIndex() >= 0) {
4690
+ <mat-tab-nav-panel
4691
+ #tabPanel
4692
+ [class.praxis-tabs-nav-panel--header-only]="!shouldRenderNavBody()"
4693
+ >
4694
+ @if (shouldRenderNavBody() && currentNavIndex() >= 0) {
4674
4695
  <div class="praxis-tabnav-content">
4675
4696
  @if (config?.nav?.links?.[currentNavIndex()]; as l) {
4676
4697
  @if ((l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex())) {
@@ -4705,6 +4726,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4705
4726
  </mat-tab-nav-panel>
4706
4727
  } @else {
4707
4728
  <mat-tab-group
4729
+ [class.praxis-tabs-group--header-only]="!shouldRenderGroupBody()"
4708
4730
  [dynamicHeight]="config?.group?.dynamicHeight"
4709
4731
  [disableRipple]="config?.group?.disableRipple"
4710
4732
  [disablePagination]="config?.group?.disablePagination"
@@ -4776,6 +4798,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4776
4798
  </button>
4777
4799
  }
4778
4800
  </ng-template>
4801
+ @if (shouldRenderGroupBody()) {
4779
4802
  @if ((entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index)) {
4780
4803
  @if (entry.tab.content) {
4781
4804
  <ng-container
@@ -4802,6 +4825,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4802
4825
  [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4803
4826
  ></praxis-empty-state-card>
4804
4827
  }
4828
+ }
4805
4829
  </mat-tab>
4806
4830
  }
4807
4831
  </mat-tab-group>
@@ -4833,7 +4857,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4833
4857
  </button>
4834
4858
  }
4835
4859
  </div>
4836
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
4860
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.praxis-tabs-group--header-only .mat-mdc-tab-body-wrapper,.praxis-tabs-nav-panel--header-only{display:none}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
4837
4861
  }], propDecorators: { config: [{
4838
4862
  type: Input
4839
4863
  }], tabsId: [{
@@ -5094,7 +5118,7 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
5094
5118
  name: 'selectedIndex',
5095
5119
  type: 'number',
5096
5120
  label: 'Indice selecionado',
5097
- description: 'Indice ativo de abas/nav controlavel externamente pela composicao',
5121
+ description: 'Indice ativo de abas/nav controlavel externamente pela composicao. Para usar tabs como header de navegacao com conteudo externo, configure group.renderBody=false ou nav.renderBody=false.',
5098
5122
  },
5099
5123
  {
5100
5124
  name: 'enableCustomization',
@@ -5277,6 +5301,7 @@ const PRAXIS_TABS_AUTHORING_MANIFEST = {
5277
5301
  { kind: 'visibility', resolver: 'tab-or-link-by-id', description: 'Runtime visibility flag for a group tab or nav link.' },
5278
5302
  { kind: 'disabledState', resolver: 'tab-or-link-by-id', description: 'Disabled state of a tab or nav link.' },
5279
5303
  { kind: 'layout', resolver: 'tabs-layout-config', description: 'Group/nav mode, header position, density, stretch and behavior settings.' },
5304
+ { kind: 'renderBody', resolver: 'tabs-render-body-config', description: 'Config paths group.renderBody and nav.renderBody that let the host own external body content.' },
5280
5305
  ],
5281
5306
  operations: [
5282
5307
  {
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@praxisui/tabs",
3
- "version": "9.0.0-beta.15",
3
+ "version": "9.0.0-beta.17",
4
4
  "description": "Configurable tabs (group and nav) for Praxis UI with metadata-driven content and runtime editor.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
8
  "@angular/material": "^21.0.0",
9
9
  "@angular/cdk": "^21.0.0",
10
- "@praxisui/core": "^9.0.0-beta.15",
11
- "@praxisui/dynamic-fields": "^9.0.0-beta.15",
12
- "@praxisui/settings-panel": "^9.0.0-beta.15",
10
+ "@praxisui/core": "^9.0.0-beta.17",
11
+ "@praxisui/dynamic-fields": "^9.0.0-beta.17",
12
+ "@praxisui/settings-panel": "^9.0.0-beta.17",
13
13
  "@angular/forms": "^21.0.0",
14
14
  "@angular/router": "^21.0.0",
15
- "@praxisui/ai": "^9.0.0-beta.15",
15
+ "@praxisui/ai": "^9.0.0-beta.17",
16
16
  "rxjs": "~7.8.0"
17
17
  },
18
18
  "dependencies": {
@@ -22,8 +22,8 @@ source_of_truth:
22
22
  - "projects/praxis-tabs/src/lib/praxis-tabs.ts"
23
23
  - "projects/praxis-tabs/src/lib/quick-setup/tabs-quick-setup.component.ts"
24
24
  - "projects/praxis-tabs/src/lib/ai/tabs-ai-capabilities.ts"
25
- source_of_truth_last_verified: "2026-03-05"
26
- last_updated: "2026-03-05"
25
+ source_of_truth_last_verified: "2026-06-24"
26
+ last_updated: "2026-06-24"
27
27
  toc: true
28
28
  sidebar: true
29
29
  tags:
@@ -90,9 +90,11 @@ Este documento e a referencia canonica da API JSON de praxis-tabs-config-editor.
90
90
  | `accessibility.highContrast/reduceMotion` | not-specified | not-specified | n/a | Active | Preservado da documentação anterior. |
91
91
  | `accessibility.ariaLabels/keyboardNavigation` | not-specified | not-specified | n/a | Declared-only | Preservado no contrato JSON, sem superficie ativa na UI. |
92
92
  | `group.*` | not-specified | not-specified | n/a | Partial | Preservado da documentação anterior. |
93
+ | `group.renderBody` | boolean | no | true | Active | Quando `false`, o group mode renderiza apenas o header de abas; o conteudo ativo fica sob controle externo do host. |
93
94
  | `tabs[]` | not-specified | not-specified | n/a | Active | Preservado da documentação anterior. |
94
95
  | `tabs[].content` | not-specified | not-specified | n/a | Partial | Preservado da documentação anterior. |
95
96
  | `nav.*` | not-specified | not-specified | n/a | Active | Preservado da documentação anterior. |
97
+ | `nav.renderBody` | boolean | no | true | Active | Quando `false`, o nav mode renderiza apenas o header de links; o conteudo ativo fica sob controle externo do host. |
96
98
  | `nav.links[]` | not-specified | not-specified | n/a | Active | Preservado da documentação anterior. |
97
99
 
98
100
  ### Supported legacy paths
@@ -493,11 +495,13 @@ Risco conhecido:
493
495
  | `group.disablePagination` | Active | guia Grupo |
494
496
  | `group.disableRipple` | Active | guia Grupo |
495
497
  | `group.preserveContent` | Active | guia Grupo |
498
+ | `group.renderBody` | Active | guia Grupo |
496
499
  | `group.stretchTabs` | Active | guia Grupo |
497
500
  | `nav.selectedIndex` | Active | guia Navegacao |
498
501
  | `nav.animationDuration` | Active | guia Navegacao |
499
502
  | `nav.ariaLabel` | Active | guia Navegacao |
500
503
  | `nav.ariaLabelledby` | Active | guia Navegacao |
504
+ | `nav.renderBody` | Active | guia Navegacao |
501
505
  | `nav.color` | Active | guia Navegacao |
502
506
  | `nav.backgroundColor` | Active | guia Navegacao |
503
507
  | `nav.fitInkBarToContent` | Active | guia Navegacao |
@@ -98,6 +98,8 @@ interface TabGroupMetadata {
98
98
  fitInkBarToContent?: boolean;
99
99
  headerPosition?: 'above' | 'below';
100
100
  preserveContent?: boolean;
101
+ /** Quando false, renderiza apenas o header de abas; o conteudo fica sob controle externo do host. */
102
+ renderBody?: boolean;
101
103
  selectedIndex?: number;
102
104
  stretchTabs?: boolean;
103
105
  backgroundColor?: 'primary' | 'accent' | 'warn' | undefined;
@@ -127,6 +129,8 @@ interface TabNavMetadata {
127
129
  disablePagination?: boolean;
128
130
  disableRipple?: boolean;
129
131
  fitInkBarToContent?: boolean;
132
+ /** Quando false, renderiza apenas o header de nav; o conteudo fica sob controle externo do host. */
133
+ renderBody?: boolean;
130
134
  selectedIndex?: number;
131
135
  stretchTabs?: boolean;
132
136
  links: TabLinkMetadata[];
@@ -276,6 +280,8 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
276
280
  applyConfigFromAdapter(next: TabsMetadata): void;
277
281
  private isLazy;
278
282
  protected groupContentReady(index: number): boolean;
283
+ protected shouldRenderGroupBody(): boolean;
284
+ protected shouldRenderNavBody(): boolean;
279
285
  protected navContentReady(index: number): boolean;
280
286
  protected isEmptyGlobal(): boolean;
281
287
  protected trackVisibleNavLink(index: number, entry: {