@praxisui/tabs 8.0.0-beta.2 → 8.0.0-beta.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
@@ -75,13 +75,46 @@ Inputs
75
75
  - `componentInstanceId?: string` Opcional para desambiguar múltiplas instâncias com o mesmo `tabsId` na mesma rota.
76
76
  - `form?: FormGroup` FormGroup opcional para campos dinâmicos declarados em `content`.
77
77
  - `context?: any` Contexto propagado a widgets internos (via `DynamicWidgetLoader`).
78
+ - `selectedIndex?: number` Índice ativo controlado por composição; não reemite `selectedIndexChange`.
78
79
  - `enableCustomization?: boolean` Exibe botão de edição quando verdadeiro (abre o editor).
79
80
 
80
81
  Outputs
81
82
  - `selectedIndexChange: number` Índice selecionado atualizado (ambos modos).
82
83
  - `selectedTabChange: MatTabChangeEvent` Evento nativo do MatTabGroup.
83
84
  - `focusChange, animationDone, indexFocused, selectFocusedIndex` Eventos nativos do Angular Material.
84
- - `widgetEvent: { tabId?, tabIndex?, linkId?, linkIndex?, sourceId, output?, payload? }` Reemissão de eventos dos widgets internos com contexto da aba/link.
85
+ - `widgetEvent: WidgetEventEnvelope` Bridge avançada/legado para transporte de eventos dos widgets internos com contexto da aba/link. Para conexões novas de widgets internos, use `composition.links` com `component-port + nestedPath`.
86
+
87
+ ## Uso Controlado por Composição
88
+
89
+ `selectedIndex` é a porta pública para controlar a aba ativa a partir de estado externo ou de outro componente. Quando esse input é aplicado, o componente atualiza a aba ativa sem reemitir `selectedIndexChange`, evitando ciclos entre `state -> selectedIndex` e `selectedIndexChange -> state`.
90
+
91
+ Padrão recomendado para páginas dinâmicas:
92
+
93
+ - grave a seleção do usuário com `selectedIndexChange -> state`;
94
+ - projete o estado de volta com `state -> selectedIndex`;
95
+ - declare `selectedIndex` depois de `config` em `bindingOrder`, para que a configuração seja carregada antes da seleção controlada;
96
+ - não persista `inputs.selectedIndex` estático em recipes quando a seleção vem de `composition.links`.
97
+
98
+ Quando existe configuração persistida por `tabsId`, o valor controlado por `selectedIndex` vence a restauração local depois do carregamento da config. Essa projeção controlada não grava uma nova preferência no storage; somente interações diretas do usuário persistem seleção. Isso mantém o estado canônico da composição como fonte de verdade da navegação ativa sem transformar navegação transitória em configuração salva. Em modo `nav`, o índice controla `nav.links`; em modo `group`, controla `tabs`.
99
+
100
+ Exemplo canônico:
101
+
102
+ ```json
103
+ {
104
+ "links": [
105
+ {
106
+ "id": "tabs.selectedIndexChange->state.navigation.activeTabIndex",
107
+ "from": { "kind": "component-port", "ref": { "widget": "workspaceTabs", "port": "selectedIndexChange" } },
108
+ "to": { "kind": "state", "ref": { "path": "navigation.activeTabIndex", "layer": "values", "write": true } }
109
+ },
110
+ {
111
+ "id": "state.navigation.activeTabIndex->tabs.selectedIndex",
112
+ "from": { "kind": "state", "ref": { "path": "navigation.activeTabIndex", "layer": "values" } },
113
+ "to": { "kind": "component-port", "ref": { "widget": "workspaceTabs", "port": "selectedIndex" } }
114
+ }
115
+ ]
116
+ }
117
+ ```
85
118
 
86
119
  Persistência
87
120
  - Quando `tabsId` é fornecido, a configuração é salva/recuperada em `AsyncConfigStorage` na chave `tabs:<component_id>`.
@@ -110,8 +143,8 @@ export interface TabsMetadata {
110
143
  behavior?: { lazyLoad?: boolean; closeable?: boolean; reorderable?: boolean };
111
144
  accessibility?: { highContrast?: boolean; reduceMotion?: boolean };
112
145
  group?: { alignTabs?: 'start' | 'center' | 'end'; headerPosition?: 'above'|'below'; selectedIndex?: number; dynamicHeight?: boolean; disableRipple?: boolean; disablePagination?: boolean; fitInkBarToContent?: boolean; stretchTabs?: boolean; color?: 'primary'|'accent'|'warn'; backgroundColor?: 'primary'|'accent'|'warn'|undefined; animationDuration?: string; ariaLabel?: string; ariaLabelledby?: string; };
113
- tabs?: Array<{ id?: string; textLabel?: string; disabled?: boolean; labelClass?: string|string[]; bodyClass?: string|string[]; content?: any[]; widgets?: WidgetDefinition[] }>;
114
- nav?: { links: Array<{ id?: string; label: string; disabled?: boolean; content?: any[]; widgets?: WidgetDefinition[] }>; selectedIndex?: number; disableRipple?: boolean; disablePagination?: boolean; fitInkBarToContent?: boolean; stretchTabs?: boolean; color?: 'primary'|'accent'|'warn'; backgroundColor?: 'primary'|'accent'|'warn'|undefined; animationDuration?: string; ariaLabel?: string; ariaLabelledby?: string };
146
+ tabs?: Array<{ id?: string; textLabel?: string; icon?: string; disabled?: boolean; visible?: boolean; labelClass?: string|string[]; bodyClass?: string|string[]; content?: any[]; widgets?: WidgetDefinition[] }>;
147
+ nav?: { links: Array<{ id?: string; label: string; icon?: string; disabled?: boolean; visible?: boolean; content?: any[]; widgets?: WidgetDefinition[] }>; selectedIndex?: number; disableRipple?: boolean; disablePagination?: boolean; fitInkBarToContent?: boolean; stretchTabs?: boolean; color?: 'primary'|'accent'|'warn'; backgroundColor?: 'primary'|'accent'|'warn'|undefined; animationDuration?: string; ariaLabel?: string; ariaLabelledby?: string };
115
148
  }
116
149
  ```
117
150
 
@@ -146,18 +179,49 @@ Quick Setup
146
179
  - `openQuickSetup()` abre `TabsQuickSetupComponent` para criação rápida de abas/links.
147
180
  - `applied$`/`saved$` aplicam a configuração ao componente.
148
181
 
182
+ ## Agentic Authoring
183
+
184
+ `@praxisui/tabs` publica `PRAXIS_TABS_AUTHORING_MANIFEST` para orientar edições assistidas por IA sobre `TabsMetadata`.
185
+
186
+ - **Editable targets:** `tab`, `tabLabel`, `tabIcon`, `tabContent`, `activeTab`, `visibility`, `disabledState` e `layout`.
187
+ - **Operation families:** `tab.add`, `tab.remove`, `tab.label.set`, `tab.icon.set`, `tab.order.set`, `tab.disabled.set`, `tab.visible.set`, `tab.active.set`, `layout.variant.set` e `tab.content.set`.
188
+ - **Validation:** ids de abas/links devem ser estáveis e únicos, remoção destrutiva exige confirmação, `group` e `nav` não devem virar modos primários concorrentes, e o round-trip precisa preservar ordem, ids e selected index.
189
+ - **Runtime/editor parity:** `tabs[].icon`, `tabs[].visible`, `nav.links[].icon` e `nav.links[].visible` são campos canônicos editáveis; itens com `visible: false` não são renderizados na navegação.
190
+ - **Registry projection:** o manifesto é exportado no `public-api` e projetado em `components['praxis-tabs'].authoringManifest` pelo AI Registry.
191
+
149
192
  ## Eventos e Conexões
150
193
 
151
- `widgetEvent` reemite eventos de widgets internos com contexto da origem, permitindo conexões no Builder/Graph:
194
+ Para conexoes canonicas de widgets internos, use `composition.links` com endpoint `component-port + nestedPath`.
195
+
196
+ Exemplo de output de uma lista dentro da primeira tab:
197
+
198
+ ```json
199
+ {
200
+ "kind": "component-port",
201
+ "ref": {
202
+ "widget": "tabs-widget",
203
+ "nestedPath": [
204
+ { "kind": "tab", "id": "employees-list", "index": 0 },
205
+ { "kind": "widget", "key": "employees-list", "componentType": "praxis-list" }
206
+ ],
207
+ "port": "itemClick",
208
+ "direction": "output"
209
+ }
210
+ }
211
+ ```
212
+
213
+ Regras:
214
+
215
+ - `ref.widget` e a instancia top-level de `praxis-tabs`;
216
+ - `nestedPath` e relativo a essa instancia e deve terminar em `kind: "widget"` com `key` estavel;
217
+ - `ref.port` e a porta real do widget filho;
218
+ - inputs para filhos nested devem atualizar a configuracao declarativa do filho, nao depender de dot-path publico sobre `config`.
219
+
220
+ `widgetEvent` continua existindo como bridge avancada/legado para transporte de eventos internos com contexto da origem:
152
221
 
153
222
  - Forma do evento: `{ tabId?, tabIndex?, linkId?, linkIndex?, sourceId, output?, payload }`.
154
- - De um componente interno para fora:
155
- - From: `{ widget: '<key do tabs>', output: 'widgetEvent' }`
156
- - Map (exemplo tabela interna): `payload.payload.id`
157
- - To: `<widget externo>.<input>`
158
- - De fora para um componente interno (dot-path):
159
- - Grupo: `inputs.config.tabs[<idx>].widgets[<widx>].inputs.<input>`
160
- - Nav: `inputs.config.nav.links[<idx>].widgets[<widx>].inputs.<input>`
223
+ - Nao use `widgetEvent` como caminho principal para authoring novo de nested ports.
224
+ - Nao use dot-path de `config.tabs[].widgets[]` como contrato publico para novos links nested.
161
225
 
162
226
  ## Lazy Load
163
227
 
@@ -167,7 +231,7 @@ Quick Setup
167
231
  ## Exemplo Mínimo
168
232
 
169
233
  ```html
170
- <praxis-tabs [config]="tabsCfg" [tabsId]="'cliente-tabs'" [enableCustomization]="true" (widgetEvent)="onWidgetEvent($event)"></praxis-tabs>
234
+ <praxis-tabs [config]="tabsCfg" [tabsId]="'cliente-tabs'" [enableCustomization]="true"></praxis-tabs>
171
235
  ```
172
236
 
173
237
  ```ts