@praxisui/tabs 8.0.0-beta.25 → 8.0.0-beta.27

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
@@ -82,6 +82,7 @@ Outputs
82
82
  - `selectedIndexChange: number` Índice selecionado atualizado (ambos modos).
83
83
  - `selectedTabChange: MatTabChangeEvent` Evento nativo do MatTabGroup.
84
84
  - `focusChange, animationDone, indexFocused, selectFocusedIndex` Eventos nativos do Angular Material.
85
+ - `configChange: TabsConfigChangeEvent` Emite `inputPatch.config` quando autoria assistida altera `TabsMetadata`, permitindo que hosts persistam a nova configuração no manifesto/página canônica.
85
86
  - `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
 
87
88
  ## Uso Controlado por Composição
@@ -187,6 +188,7 @@ Quick Setup
187
188
  - **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
189
  - **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
190
  - **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.
191
+ - **Current-area authoring:** pedidos como “nesta aba”, “aba atual” ou “dentro da aba” devem materializar `tab.content.set` no item ativo/resolvido, não `tab.add`; criar nova aba exige intenção explícita como “nova aba” ou “adicionar aba”.
190
192
  - **Registry projection:** o manifesto é exportado no `public-api` e projetado em `components['praxis-tabs'].authoringManifest` pelo AI Registry.
191
193
 
192
194
  ### Praxis Semantic Assistant
@@ -14,7 +14,7 @@ import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, providePraxi
14
14
  import * as i6$1 from '@angular/material/button';
15
15
  import { MatButtonModule } from '@angular/material/button';
16
16
  import * as i3 from '@angular/forms';
17
- import { FormsModule, ReactiveFormsModule } from '@angular/forms';
17
+ import { FormsModule, FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
18
18
  import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
19
19
  import { SETTINGS_PANEL_DATA, SettingsPanelService } from '@praxisui/settings-panel';
20
20
  import * as i5 from '@angular/material/form-field';
@@ -28,8 +28,8 @@ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
28
28
  import { BehaviorSubject, firstValueFrom, Subject, Subscription } from 'rxjs';
29
29
  import { produce } from 'immer';
30
30
  import { MatSnackBar } from '@angular/material/snack-bar';
31
- import { take, takeUntil } from 'rxjs/operators';
32
- import { BaseAiAdapter, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
31
+ import { takeUntil, take } from 'rxjs/operators';
32
+ import { BaseAiAdapter, shouldRoutePromptToGovernedDecision, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
33
33
 
34
34
  const DOCUMENT_KIND = 'praxis.tabs.editor';
35
35
  const DOCUMENT_VERSION = 1;
@@ -375,6 +375,13 @@ const PRAXIS_TABS_PT_BR = {
375
375
  'quickSetup.mode.nav': 'Navegação por links',
376
376
  'quickSetup.fields.addLabelPlaceholder': 'Ex.: Dados Gerais',
377
377
  'quickSetup.hints.emptyLabels': 'Adicione um ou mais rótulos para criar as abas.',
378
+ 'ai.review.ready': 'Plano de edição das abas pronto para revisar.',
379
+ 'ai.review.unsupportedPlan': 'O componentEditPlan das abas não possui operações suportadas para aplicar.',
380
+ 'ai.review.addTab': 'Adicionar aba: {{label}}.',
381
+ 'ai.review.renameTab': 'Renomear aba para: {{label}}.',
382
+ 'ai.review.singleAdjustment': 'Revisar ajuste da aba: {{label}}.',
383
+ 'ai.review.multipleAdjustments': 'Revisar {{count}} ajustes nas abas: {{items}}',
384
+ 'ai.review.multipleAdjustmentsShort': 'Revisar {{count}} ajustes nas abas.',
378
385
  };
379
386
  const PRAXIS_TABS_EN_US = {
380
387
  'settings.title': 'Configure tabs',
@@ -513,6 +520,13 @@ const PRAXIS_TABS_EN_US = {
513
520
  'quickSetup.mode.nav': 'Link navigation',
514
521
  'quickSetup.fields.addLabelPlaceholder': 'e.g. General Data',
515
522
  'quickSetup.hints.emptyLabels': 'Add one or more labels to create the tabs.',
523
+ 'ai.review.ready': 'Tabs edit plan ready to review.',
524
+ 'ai.review.unsupportedPlan': 'The tabs componentEditPlan does not contain supported operations to apply.',
525
+ 'ai.review.addTab': 'Add tab: {{label}}.',
526
+ 'ai.review.renameTab': 'Rename tab to: {{label}}.',
527
+ 'ai.review.singleAdjustment': 'Review tab adjustment: {{label}}.',
528
+ 'ai.review.multipleAdjustments': 'Review {{count}} tab adjustments: {{items}}',
529
+ 'ai.review.multipleAdjustmentsShort': 'Review {{count}} tab adjustments.',
516
530
  };
517
531
  function createPraxisTabsI18nConfig() {
518
532
  return {
@@ -2295,12 +2309,80 @@ const TABS_AI_CAPABILITIES = {
2295
2309
 
2296
2310
  class TabsAiAdapter extends BaseAiAdapter {
2297
2311
  tabs;
2312
+ translate;
2298
2313
  componentName = 'Praxis Tabs';
2299
2314
  componentId = 'praxis-tabs';
2300
2315
  componentType = 'tabs';
2301
- constructor(tabs) {
2316
+ constructor(tabs, translate) {
2302
2317
  super();
2303
2318
  this.tabs = tabs;
2319
+ this.translate = translate;
2320
+ }
2321
+ compileAiResponse(response) {
2322
+ const plan = this.toRecord(response['componentEditPlan']);
2323
+ const operations = Array.isArray(plan?.['operations']) ? plan['operations'] : [];
2324
+ if (!plan || operations.length === 0) {
2325
+ return null;
2326
+ }
2327
+ const current = this.getCurrentConfig();
2328
+ const tabsPatch = [];
2329
+ const warnings = [];
2330
+ for (const operationCandidate of operations) {
2331
+ const operation = this.toRecord(operationCandidate);
2332
+ if (!operation) {
2333
+ warnings.push('tabs-component-edit-plan-operation-invalid');
2334
+ continue;
2335
+ }
2336
+ const operationId = this.toText(operation['operationId'] ?? operation['changeKind']);
2337
+ if (operationId === 'tab.label.set') {
2338
+ const targetId = this.resolveTargetId(operation, current);
2339
+ const textLabel = this.resolveTextLabel(operation);
2340
+ if (!targetId) {
2341
+ warnings.push('tabs-component-edit-plan-target-missing');
2342
+ continue;
2343
+ }
2344
+ if (!textLabel) {
2345
+ warnings.push('tabs-component-edit-plan-text-label-missing');
2346
+ continue;
2347
+ }
2348
+ tabsPatch.push({ id: targetId, textLabel });
2349
+ continue;
2350
+ }
2351
+ if (operationId === 'tab.add') {
2352
+ const tab = this.resolveTabAddInput(operation);
2353
+ if (!tab) {
2354
+ warnings.push('tabs-component-edit-plan-tab-add-input-invalid');
2355
+ continue;
2356
+ }
2357
+ tabsPatch.push(tab);
2358
+ continue;
2359
+ }
2360
+ if (operationId === 'tab.content.set') {
2361
+ const contentPatch = this.resolveTabContentInput(operation, current);
2362
+ if (!contentPatch) {
2363
+ warnings.push('tabs-component-edit-plan-tab-content-input-invalid');
2364
+ continue;
2365
+ }
2366
+ tabsPatch.push(contentPatch);
2367
+ continue;
2368
+ }
2369
+ warnings.push(`tabs-component-edit-plan-operation-unsupported:${operationId || '<missing>'}`);
2370
+ }
2371
+ if (tabsPatch.length === 0) {
2372
+ return {
2373
+ type: 'error',
2374
+ message: this.t('ai.review.unsupportedPlan', 'O componentEditPlan das abas não possui operações suportadas para aplicar.'),
2375
+ warnings,
2376
+ };
2377
+ }
2378
+ return {
2379
+ patch: { tabs: tabsPatch },
2380
+ explanation: this.summarizeOperations(operations, tabsPatch)
2381
+ ?? (typeof response['explanation'] === 'string'
2382
+ ? response['explanation']
2383
+ : this.t('ai.review.ready', 'Plano de edição das abas pronto para revisar.')),
2384
+ warnings: warnings.length ? warnings : undefined,
2385
+ };
2304
2386
  }
2305
2387
  getCurrentConfig() {
2306
2388
  return this.cloneConfig(this.tabs.config || {});
@@ -2402,16 +2484,40 @@ class TabsAiAdapter extends BaseAiAdapter {
2402
2484
  smartMergeTabsConfig(base, patch) {
2403
2485
  const result = deepMerge(base, patch);
2404
2486
  if (patch.tabs && Array.isArray(patch.tabs)) {
2405
- const merged = this.mergeByKey(base.tabs || [], patch.tabs, (t) => t.id || t.textLabel || '');
2487
+ const patchTabs = this.coalesceByKey(patch.tabs, (t) => t.id || t.textLabel || '');
2488
+ const merged = this.mergeByKey(base.tabs || [], patchTabs, (t) => t.id || t.textLabel || '');
2406
2489
  result.tabs = merged;
2407
2490
  }
2408
2491
  const patchLinks = patch.nav?.links;
2409
2492
  if (patchLinks && Array.isArray(patchLinks)) {
2410
- const merged = this.mergeByKey(base.nav?.links || [], patchLinks, (l) => l.id || l.label || '');
2493
+ const normalizedPatchLinks = this.coalesceByKey(patchLinks, (l) => l.id || l.label || '');
2494
+ const merged = this.mergeByKey(base.nav?.links || [], normalizedPatchLinks, (l) => l.id || l.label || '');
2411
2495
  result.nav = { ...(result.nav || {}), links: merged };
2412
2496
  }
2413
2497
  return result;
2414
2498
  }
2499
+ coalesceByKey(items, keyFn) {
2500
+ const keyed = new Map();
2501
+ const result = [];
2502
+ items.forEach((item) => {
2503
+ const key = keyFn(item);
2504
+ if (!key) {
2505
+ result.push(item);
2506
+ return;
2507
+ }
2508
+ const existing = keyed.get(key);
2509
+ if (existing) {
2510
+ keyed.set(key, deepMerge(existing, item));
2511
+ return;
2512
+ }
2513
+ keyed.set(key, item);
2514
+ result.push(item);
2515
+ });
2516
+ return result.map((item) => {
2517
+ const key = keyFn(item);
2518
+ return key && keyed.has(key) ? keyed.get(key) : item;
2519
+ });
2520
+ }
2415
2521
  mergeByKey(baseArr, patchArr, keyFn) {
2416
2522
  const merged = baseArr.map((orig) => {
2417
2523
  const key = keyFn(orig);
@@ -2434,6 +2540,138 @@ class TabsAiAdapter extends BaseAiAdapter {
2434
2540
  return JSON.parse(JSON.stringify(config));
2435
2541
  }
2436
2542
  }
2543
+ resolveTargetId(operation, current) {
2544
+ const target = this.toRecord(operation['target']);
2545
+ const rawTarget = target
2546
+ ? this.toText(target['id'] ?? target['label'] ?? target['textLabel'] ?? target['value'])
2547
+ : this.toText(operation['target'] ?? operation['targetId']);
2548
+ if (!rawTarget) {
2549
+ return null;
2550
+ }
2551
+ const normalized = this.normalizeToken(rawTarget);
2552
+ const tab = (current.tabs ?? []).find((candidate) => {
2553
+ const id = this.normalizeToken(candidate.id);
2554
+ const label = this.normalizeToken(candidate.textLabel);
2555
+ return id === normalized || label === normalized;
2556
+ });
2557
+ return tab?.id || rawTarget;
2558
+ }
2559
+ resolveTextLabel(operation) {
2560
+ const input = this.toRecord(operation['input'])
2561
+ ?? this.toRecord(operation['params'])
2562
+ ?? this.toRecord(operation['payload']);
2563
+ const value = input
2564
+ ? this.toText(input['textLabel'] ?? input['label'] ?? input['value'])
2565
+ : this.toText(operation['textLabel'] ?? operation['label'] ?? operation['value']);
2566
+ return value?.trim() || null;
2567
+ }
2568
+ resolveTabAddInput(operation) {
2569
+ const input = this.toRecord(operation['input'])
2570
+ ?? this.toRecord(operation['params'])
2571
+ ?? this.toRecord(operation['payload']);
2572
+ if (!input) {
2573
+ return null;
2574
+ }
2575
+ const id = this.toText(input['id']);
2576
+ const textLabel = this.toText(input['textLabel'] ?? input['label']);
2577
+ if (!id || !textLabel) {
2578
+ return null;
2579
+ }
2580
+ const tab = { id, textLabel };
2581
+ for (const key of ['icon', 'disabled', 'visible', 'content', 'widgets']) {
2582
+ if (input[key] !== undefined) {
2583
+ tab[key] = input[key];
2584
+ }
2585
+ }
2586
+ return tab;
2587
+ }
2588
+ resolveTabContentInput(operation, current) {
2589
+ const targetId = this.resolveTargetId(operation, current);
2590
+ if (!targetId) {
2591
+ return null;
2592
+ }
2593
+ const input = this.toRecord(operation['input'])
2594
+ ?? this.toRecord(operation['params'])
2595
+ ?? this.toRecord(operation['payload']);
2596
+ if (!input) {
2597
+ return null;
2598
+ }
2599
+ const patch = { id: targetId };
2600
+ const currentTab = (current.tabs ?? []).find(tab => tab.id === targetId);
2601
+ if (currentTab?.textLabel) {
2602
+ patch.textLabel = currentTab.textLabel;
2603
+ }
2604
+ for (const key of ['content', 'widgets']) {
2605
+ if (Array.isArray(input[key])) {
2606
+ patch[key] = input[key];
2607
+ }
2608
+ }
2609
+ return patch.content || patch.widgets ? patch : null;
2610
+ }
2611
+ summarizeOperations(operations, tabsPatch) {
2612
+ const summaries = [];
2613
+ for (const operationCandidate of operations) {
2614
+ const operation = this.toRecord(operationCandidate);
2615
+ if (!operation) {
2616
+ continue;
2617
+ }
2618
+ const operationId = this.toText(operation['operationId'] ?? operation['changeKind']);
2619
+ if (operationId === 'tab.add') {
2620
+ const tab = this.resolveTabAddInput(operation);
2621
+ if (tab?.textLabel) {
2622
+ summaries.push(this.t('ai.review.addTab', 'Adicionar aba: {{label}}.', { label: tab.textLabel }));
2623
+ }
2624
+ continue;
2625
+ }
2626
+ if (operationId === 'tab.label.set') {
2627
+ const textLabel = this.resolveTextLabel(operation);
2628
+ if (textLabel) {
2629
+ summaries.push(this.t('ai.review.renameTab', 'Renomear aba para: {{label}}.', { label: textLabel }));
2630
+ }
2631
+ continue;
2632
+ }
2633
+ if (operationId === 'tab.content.set') {
2634
+ const contentPatch = this.resolveTabContentInput(operation, this.getCurrentConfig());
2635
+ if (contentPatch?.id) {
2636
+ summaries.push(this.t('ai.review.setTabContent', 'Atualizar conteúdo da aba: {{label}}.', { label: contentPatch.textLabel || contentPatch.id }));
2637
+ }
2638
+ }
2639
+ }
2640
+ if (summaries.length === 1) {
2641
+ return summaries[0];
2642
+ }
2643
+ if (summaries.length > 1) {
2644
+ return this.t('ai.review.multipleAdjustments', 'Revisar {{count}} ajustes nas abas: {{items}}', { count: summaries.length, items: summaries.join(' ') });
2645
+ }
2646
+ if (tabsPatch.length === 1) {
2647
+ return this.t('ai.review.singleAdjustment', 'Revisar ajuste da aba: {{label}}.', { label: tabsPatch[0].textLabel || tabsPatch[0].id });
2648
+ }
2649
+ return tabsPatch.length > 1
2650
+ ? this.t('ai.review.multipleAdjustmentsShort', 'Revisar {{count}} ajustes nas abas.', { count: tabsPatch.length })
2651
+ : null;
2652
+ }
2653
+ t(key, fallback, params) {
2654
+ return this.translate ? this.translate(key, fallback, params) : this.interpolate(fallback, params);
2655
+ }
2656
+ interpolate(template, params) {
2657
+ if (!params) {
2658
+ return template;
2659
+ }
2660
+ return template.replace(/\{\{\s*([.\w-]+)\s*\}\}/g, (_, key) => String(params[key] ?? `{{${key}}}`));
2661
+ }
2662
+ toRecord(value) {
2663
+ return value && typeof value === 'object' && !Array.isArray(value)
2664
+ ? value
2665
+ : null;
2666
+ }
2667
+ toText(value) {
2668
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
2669
+ }
2670
+ normalizeToken(value) {
2671
+ return typeof value === 'string'
2672
+ ? value.trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '')
2673
+ : '';
2674
+ }
2437
2675
  }
2438
2676
 
2439
2677
  class TabsAgenticAuthoringTurnFlow {
@@ -2485,12 +2723,35 @@ class TabsAgenticAuthoringTurnFlow {
2485
2723
  }));
2486
2724
  return this.toTurnResult(this.compileAdapterResponse(response), request);
2487
2725
  }
2488
- async apply(_request) {
2726
+ async apply(request) {
2727
+ const patch = this.toRecord(request.pendingPatch);
2728
+ if (patch) {
2729
+ const result = await this.adapter.applyPatch(patch, request.prompt);
2730
+ if (!result.success) {
2731
+ return {
2732
+ state: 'error',
2733
+ phase: 'apply',
2734
+ assistantMessage: result.error || 'Nao foi possivel aplicar as alteracoes nas abas.',
2735
+ errorText: result.error || 'Nao foi possivel aplicar as alteracoes nas abas.',
2736
+ canApply: true,
2737
+ pendingPatch: patch,
2738
+ };
2739
+ }
2740
+ return {
2741
+ state: 'success',
2742
+ phase: 'summarize',
2743
+ assistantMessage: 'Alteracoes aplicadas nas abas.',
2744
+ statusText: 'Alteracoes aplicadas nas abas.',
2745
+ canApply: false,
2746
+ pendingPatch: null,
2747
+ diagnostics: result.warnings?.length ? { warnings: result.warnings } : undefined,
2748
+ };
2749
+ }
2489
2750
  return {
2490
2751
  state: 'error',
2491
2752
  phase: 'apply',
2492
- assistantMessage: 'As abas ainda exigem componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
2493
- errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-tabs.',
2753
+ assistantMessage: 'Nao ha alteracao de abas pronta para aplicar.',
2754
+ errorText: 'Nao ha alteracao de abas pronta para aplicar.',
2494
2755
  canApply: false,
2495
2756
  pendingPatch: null,
2496
2757
  };
@@ -2554,10 +2815,29 @@ class TabsAgenticAuthoringTurnFlow {
2554
2815
  sessionId: response.sessionId ?? request.sessionId,
2555
2816
  assistantMessage: message,
2556
2817
  errorText: message,
2818
+ canApply: false,
2819
+ pendingPatch: null,
2557
2820
  diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
2558
2821
  };
2559
2822
  }
2560
2823
  if (response.patch && Object.keys(response.patch).length > 0) {
2824
+ if (response.componentEditPlan) {
2825
+ const warnings = response.warnings?.filter(Boolean) ?? [];
2826
+ return {
2827
+ state: 'review',
2828
+ phase: 'review',
2829
+ sessionId: response.sessionId ?? request.sessionId,
2830
+ assistantMessage: response.explanation || 'Proposta de abas pronta para revisar.',
2831
+ statusText: 'Revise a proposta antes de aplicar.',
2832
+ canApply: true,
2833
+ pendingPatch: response.patch,
2834
+ preview: {
2835
+ kind: 'tabs-config-patch',
2836
+ diff: response.diff ?? [],
2837
+ },
2838
+ diagnostics: warnings.length ? { warnings } : undefined,
2839
+ };
2840
+ }
2561
2841
  return {
2562
2842
  state: 'error',
2563
2843
  phase: 'review',
@@ -2585,6 +2865,16 @@ class TabsAgenticAuthoringTurnFlow {
2585
2865
  }
2586
2866
  compileAdapterResponse(response) {
2587
2867
  const compiled = this.adapter.compileAiResponse?.(response);
2868
+ if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
2869
+ return {
2870
+ type: 'error',
2871
+ message: 'Patch livre de abas rejeitado. Gere um componentEditPlan validado pelo manifesto antes de propor alteracao local.',
2872
+ warnings: [
2873
+ 'free-tabs-patch-rejected',
2874
+ 'Use componentEditPlan validado contra PRAXIS_TABS_AUTHORING_MANIFEST.',
2875
+ ],
2876
+ };
2877
+ }
2588
2878
  if (!compiled) {
2589
2879
  return response;
2590
2880
  }
@@ -2666,28 +2956,7 @@ class TabsAgenticAuthoringTurnFlow {
2666
2956
  return rowCount > 0 ? { rowCount } : {};
2667
2957
  }
2668
2958
  shouldRouteToGovernedDecision(prompt, contextHints) {
2669
- const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
2670
- const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
2671
- if (recommendedFlow === 'shared_rule_authoring')
2672
- return true;
2673
- return [
2674
- 'regra',
2675
- 'politica',
2676
- 'policy',
2677
- 'compliance',
2678
- 'lgpd',
2679
- 'privacidade',
2680
- 'aprovacao',
2681
- 'aprovar',
2682
- 'publicar',
2683
- 'materializar',
2684
- 'enforcement',
2685
- 'validacao de negocio',
2686
- 'validar negocio',
2687
- 'elegibilidade',
2688
- 'permissao',
2689
- 'acesso',
2690
- ].some((term) => normalized.includes(term));
2959
+ return shouldRoutePromptToGovernedDecision(prompt, contextHints);
2691
2960
  }
2692
2961
  toGovernedDecisionHandoff(prompt, request) {
2693
2962
  const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. As abas podem ajudar a localizar a experiencia afetada, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
@@ -2770,6 +3039,7 @@ class TabsAgenticAuthoringTurnFlow {
2770
3039
  }
2771
3040
  }
2772
3041
 
3042
+ const AGENTIC_PAGE_COMPOSITION_REQUEST_OUTPUT = 'agenticPageCompositionRequested';
2773
3043
  class PraxisTabs {
2774
3044
  i18n = inject(PraxisI18nService);
2775
3045
  settings = inject(SettingsPanelService);
@@ -2796,6 +3066,7 @@ class PraxisTabs {
2796
3066
  }
2797
3067
  }, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
2798
3068
  warnedMissingId = false;
3069
+ loadedStorageKey = null;
2799
3070
  config = null;
2800
3071
  tabsId;
2801
3072
  componentInstanceId;
@@ -2814,8 +3085,9 @@ class PraxisTabs {
2814
3085
  selectedTabChange = new EventEmitter();
2815
3086
  indexFocused = new EventEmitter();
2816
3087
  selectFocusedIndex = new EventEmitter();
3088
+ configChange = new EventEmitter();
2817
3089
  widgetEvent = new EventEmitter();
2818
- aiAdapter = new TabsAiAdapter(this);
3090
+ aiAdapter = new TabsAiAdapter(this, (key, fallback, params) => this.i18n.t(key, params, fallback, PRAXIS_TABS_I18N_NAMESPACE));
2819
3091
  aiAssistantOpen = false;
2820
3092
  aiAssistantPrompt = '';
2821
3093
  aiAssistantViewState = null;
@@ -2839,24 +3111,20 @@ class PraxisTabs {
2839
3111
  controlledSelectedIndex;
2840
3112
  destroy$ = new Subject();
2841
3113
  widgetDefinitionCache = new WeakMap();
3114
+ generatedContentForm = new FormGroup({});
2842
3115
  ngOnInit() {
2843
3116
  this.syncSelectionFromConfig();
2844
- // Load stored config if tabsId provided
2845
- const key = this.storageKey();
2846
- if (key) {
2847
- this.storage.loadConfig(key).pipe(take(1)).subscribe((stored) => {
2848
- if (stored) {
2849
- this.config = stored;
2850
- }
2851
- this.syncSelectionFromConfig();
2852
- this.reapplyControlledSelectedIndex();
2853
- });
2854
- }
3117
+ this.syncGeneratedContentForm();
3118
+ this.loadStoredConfigForCurrentIdentity({ warnMissingId: false });
2855
3119
  }
2856
3120
  ngOnChanges(changes) {
3121
+ if (changes['tabsId'] || changes['componentInstanceId']) {
3122
+ this.loadStoredConfigForCurrentIdentity({ warnMissingId: false });
3123
+ }
2857
3124
  if (changes['config'] && this.config) {
2858
3125
  // Reset loaded caches on config change and seed with current selections
2859
3126
  this.syncSelectionFromConfig();
3127
+ this.syncGeneratedContentForm();
2860
3128
  // Persist when tabsId provided
2861
3129
  this.persistConfig(this.config);
2862
3130
  this.reapplyControlledSelectedIndex();
@@ -2900,6 +3168,9 @@ class PraxisTabs {
2900
3168
  const index = entries.findIndex((entry) => entry.index === this.selectedIndexSignal());
2901
3169
  return index >= 0 ? index : 0;
2902
3170
  }
3171
+ effectiveContentForm() {
3172
+ return this.form || this.generatedContentForm;
3173
+ }
2903
3174
  onVisibleTabIndexChange(index) {
2904
3175
  const entry = this.visibleTabEntries()[index];
2905
3176
  if (!entry)
@@ -3129,6 +3400,12 @@ class PraxisTabs {
3129
3400
  this.syncAiAssistantSession();
3130
3401
  }
3131
3402
  onAiAssistantSubmit(prompt) {
3403
+ if (this.shouldDelegateAiAssistantPromptToPageBuilder(prompt)) {
3404
+ this.emitAgenticPageCompositionRequest(prompt);
3405
+ this.aiAssistantPrompt = '';
3406
+ this.closeAiAssistant();
3407
+ return;
3408
+ }
3132
3409
  this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
3133
3410
  this.aiAssistantPrompt = '';
3134
3411
  this.aiAssistantViewState = state;
@@ -3415,6 +3692,7 @@ class PraxisTabs {
3415
3692
  draft.group.selectedIndex = (draft.tabs.length || 1) - 1;
3416
3693
  });
3417
3694
  this.config = next;
3695
+ this.syncGeneratedContentForm();
3418
3696
  this.persistConfig(this.config);
3419
3697
  this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
3420
3698
  }
@@ -3476,6 +3754,7 @@ class PraxisTabs {
3476
3754
  }
3477
3755
  this.config = plan.canonicalConfig;
3478
3756
  this.syncSelectionFromConfig();
3757
+ this.syncGeneratedContentForm();
3479
3758
  if (plan.runtime.rebuildLazyState) {
3480
3759
  this.groupLoaded.clear();
3481
3760
  this.navLoaded.clear();
@@ -3492,10 +3771,24 @@ class PraxisTabs {
3492
3771
  this.persistConfig(this.config);
3493
3772
  }
3494
3773
  }
3495
- storageKey() {
3496
- const id = this.componentKeyId();
3774
+ storageKey(options = {}) {
3775
+ const id = this.componentKeyId(options);
3497
3776
  return id ? `tabs:${id}` : null;
3498
3777
  }
3778
+ loadStoredConfigForCurrentIdentity(options = {}) {
3779
+ const key = this.storageKey(options);
3780
+ if (!key || key === this.loadedStorageKey)
3781
+ return;
3782
+ this.loadedStorageKey = key;
3783
+ this.storage.loadConfig(key).pipe(take(1)).subscribe((stored) => {
3784
+ if (stored) {
3785
+ this.config = stored;
3786
+ this.syncGeneratedContentForm();
3787
+ }
3788
+ this.syncSelectionFromConfig();
3789
+ this.reapplyControlledSelectedIndex();
3790
+ });
3791
+ }
3499
3792
  syncSelectionFromConfig() {
3500
3793
  const tabsLength = this.config?.tabs?.length ?? 0;
3501
3794
  const linksLength = this.config?.nav?.links?.length ?? 0;
@@ -3523,7 +3816,7 @@ class PraxisTabs {
3523
3816
  return;
3524
3817
  this.storage.saveConfig(key, config).pipe(take(1)).subscribe({ error: () => { } });
3525
3818
  }
3526
- componentKeyId() {
3819
+ componentKeyId(options = {}) {
3527
3820
  const key = this.componentKeys.buildComponentId({
3528
3821
  componentType: 'praxis-tabs',
3529
3822
  componentId: this.tabsId,
@@ -3532,7 +3825,7 @@ class PraxisTabs {
3532
3825
  route: this.route,
3533
3826
  requireComponentId: true,
3534
3827
  });
3535
- if (!key)
3828
+ if (!key && options.warnMissingId !== false)
3536
3829
  this.warnMissingId();
3537
3830
  return key;
3538
3831
  }
@@ -3562,7 +3855,9 @@ class PraxisTabs {
3562
3855
  applyConfigFromAdapter(next) {
3563
3856
  this.config = next;
3564
3857
  this.syncSelectionFromConfig();
3858
+ this.syncGeneratedContentForm();
3565
3859
  this.persistConfig(this.config);
3860
+ this.configChange.emit({ inputPatch: { config: this.cloneTabsMetadata(this.config) } });
3566
3861
  }
3567
3862
  // =====================
3568
3863
  // Lazy load helpers
@@ -3596,6 +3891,12 @@ class PraxisTabs {
3596
3891
  trackWidgetDefinition(index, widget) {
3597
3892
  return widget.childWidgetKey || widget.id || `widget:${index}`;
3598
3893
  }
3894
+ safeWidgetDefinitions(widgets) {
3895
+ if (!Array.isArray(widgets)) {
3896
+ return [];
3897
+ }
3898
+ return widgets.filter((widget) => (!!widget && typeof widget === 'object'));
3899
+ }
3599
3900
  resolveWidgetDefinition(widget) {
3600
3901
  const cached = this.widgetDefinitionCache.get(widget);
3601
3902
  if (cached) {
@@ -3605,12 +3906,67 @@ class PraxisTabs {
3605
3906
  this.widgetDefinitionCache.set(widget, clone);
3606
3907
  return clone;
3607
3908
  }
3909
+ syncGeneratedContentForm() {
3910
+ if (this.form) {
3911
+ return;
3912
+ }
3913
+ for (const field of this.collectContentFields()) {
3914
+ const name = this.resolveContentFieldName(field);
3915
+ if (!name || this.generatedContentForm.contains(name)) {
3916
+ continue;
3917
+ }
3918
+ this.generatedContentForm.addControl(name, new FormControl(field?.defaultValue ?? null));
3919
+ }
3920
+ }
3921
+ collectContentFields() {
3922
+ return [
3923
+ ...((this.config?.tabs ?? []).flatMap((tab) => tab.content ?? [])),
3924
+ ...((this.config?.nav?.links ?? []).flatMap((link) => link.content ?? [])),
3925
+ ];
3926
+ }
3927
+ resolveContentFieldName(field) {
3928
+ const name = field?.name ?? field?.key ?? field?.id;
3929
+ return typeof name === 'string' && name.trim() ? name.trim() : null;
3930
+ }
3608
3931
  emitWidgetEvent(path, ev) {
3609
3932
  this.widgetEvent.emit({
3610
3933
  ...ev,
3611
3934
  path: [...path, ...(ev.path || [])],
3612
3935
  });
3613
3936
  }
3937
+ emitAgenticPageCompositionRequest(prompt) {
3938
+ this.widgetEvent.emit({
3939
+ sourceComponentId: 'praxis-tabs',
3940
+ output: AGENTIC_PAGE_COMPOSITION_REQUEST_OUTPUT,
3941
+ payload: {
3942
+ prompt,
3943
+ source: 'praxis-tabs',
3944
+ reason: 'nested-page-composition-request',
3945
+ },
3946
+ path: [{ kind: 'tabs', id: this.tabsId }],
3947
+ });
3948
+ }
3949
+ shouldDelegateAiAssistantPromptToPageBuilder(prompt) {
3950
+ const normalized = this.normalizeAssistantPrompt(prompt);
3951
+ if (!normalized)
3952
+ return false;
3953
+ const hasCompositionVerb = /\b(ajust\w*|adicion\w*|inclu\w*|cri\w*|mont\w*|complet\w*|preench\w*|use|usar|organiz\w*)\b/.test(normalized);
3954
+ const hasTabsContainer = /\b(abas?|tabs?|cadastro|registros?|acompanhamento|solicitacoes|workspace)\b/.test(normalized);
3955
+ const hasNestedWidget = /\b(formularios?|forms?|listas?|listagem|crud|cards?|tabelas?|componentes?|widgets?)\b/.test(normalized);
3956
+ const hasStructuredContent = /\b(campos?|colunas?|sla|historico|responsavel|prioridade|prazo|titulo|status|item)\b/.test(normalized);
3957
+ const hasLocalEditorialBoundary = /\b(local|editorial|fictici\w*|mock|sem api real|nao conecte api|sem conectar api|sem schema externo)\b/.test(normalized);
3958
+ return hasCompositionVerb
3959
+ && hasTabsContainer
3960
+ && (hasNestedWidget || (hasLocalEditorialBoundary && hasStructuredContent));
3961
+ }
3962
+ normalizeAssistantPrompt(prompt) {
3963
+ return (prompt || '')
3964
+ .normalize('NFD')
3965
+ .replace(/[\u0300-\u036f]/g, '')
3966
+ .toLowerCase()
3967
+ .replace(/\s+/g, ' ')
3968
+ .trim();
3969
+ }
3614
3970
  tabEventPath(tabId, tabIndex) {
3615
3971
  return [
3616
3972
  { kind: 'tabs', id: this.tabsId },
@@ -3770,8 +4126,17 @@ class PraxisTabs {
3770
4126
  catch { }
3771
4127
  return JSON.parse(JSON.stringify(widget));
3772
4128
  }
4129
+ cloneTabsMetadata(config) {
4130
+ try {
4131
+ if (typeof structuredClone === 'function') {
4132
+ return structuredClone(config);
4133
+ }
4134
+ }
4135
+ catch { }
4136
+ return JSON.parse(JSON.stringify(config));
4137
+ }
3773
4138
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
3774
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", componentInstanceId: "componentInstanceId", selectedIndex: "selectedIndex", enableCustomization: "enableCustomization", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", widgetEvent: "widgetEvent" }, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: `
4139
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", componentInstanceId: "componentInstanceId", selectedIndex: "selectedIndex", enableCustomization: "enableCustomization", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", configChange: "configChange", widgetEvent: "widgetEvent" }, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: `
3775
4140
  <div
3776
4141
  class="praxis-tabs-root"
3777
4142
  [class.density-compact]="config?.appearance?.density === 'compact'"
@@ -3895,16 +4260,16 @@ class PraxisTabs {
3895
4260
  <ng-container
3896
4261
  *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
3897
4262
  >
3898
- <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
3899
- <ng-container *ngIf="l.content && form">
4263
+ <ng-container *ngIf="(l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex()); else emptyNav">
4264
+ <ng-container *ngIf="l.content">
3900
4265
  <ng-container
3901
4266
  dynamicFieldLoader
3902
4267
  [fields]="l.content || []"
3903
- [formGroup]="form!"
4268
+ [formGroup]="effectiveContentForm()"
3904
4269
  ></ng-container>
3905
4270
  </ng-container>
3906
- <ng-container *ngIf="l.widgets?.length">
3907
- <ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
4271
+ <ng-container *ngIf="safeWidgetDefinitions(l.widgets).length">
4272
+ <ng-container *ngFor="let w of safeWidgetDefinitions(l.widgets); let wi = index; trackBy: trackWidgetDefinition">
3908
4273
  <ng-container
3909
4274
  [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
3910
4275
  [context]="context || {}"
@@ -3999,34 +4364,32 @@ class PraxisTabs {
3999
4364
  </ng-container>
4000
4365
  </ng-template>
4001
4366
 
4002
- <ng-template matTabContent>
4003
- <ng-container *ngIf="(entry.tab.content?.length || entry.tab.widgets?.length) && groupContentReady(entry.index); else emptyTab">
4004
- <ng-container *ngIf="entry.tab.content && form">
4367
+ <ng-container *ngIf="(entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index); else emptyTab">
4368
+ <ng-container *ngIf="entry.tab.content">
4369
+ <ng-container
4370
+ dynamicFieldLoader
4371
+ [fields]="entry.tab.content || []"
4372
+ [formGroup]="effectiveContentForm()"
4373
+ ></ng-container>
4374
+ </ng-container>
4375
+ <ng-container *ngIf="safeWidgetDefinitions(entry.tab.widgets).length">
4376
+ <ng-container *ngFor="let w of safeWidgetDefinitions(entry.tab.widgets); let wi = index; trackBy: trackWidgetDefinition">
4005
4377
  <ng-container
4006
- dynamicFieldLoader
4007
- [fields]="entry.tab.content || []"
4008
- [formGroup]="form!"
4378
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
4379
+ [context]="context || {}"
4380
+ (widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
4009
4381
  ></ng-container>
4010
4382
  </ng-container>
4011
- <ng-container *ngIf="entry.tab.widgets?.length">
4012
- <ng-container *ngFor="let w of entry.tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
4013
- <ng-container
4014
- [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
4015
- [context]="context || {}"
4016
- (widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
4017
- ></ng-container>
4018
- </ng-container>
4019
- </ng-container>
4020
4383
  </ng-container>
4021
- <ng-template #emptyTab>
4022
- <praxis-empty-state-card
4023
- [inline]="true"
4024
- icon="dashboard_customize"
4025
- [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
4026
- [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
4027
- [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4028
- ></praxis-empty-state-card>
4029
- </ng-template>
4384
+ </ng-container>
4385
+ <ng-template #emptyTab>
4386
+ <praxis-empty-state-card
4387
+ [inline]="true"
4388
+ icon="dashboard_customize"
4389
+ [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
4390
+ [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
4391
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4392
+ ></praxis-empty-state-card>
4030
4393
  </ng-template>
4031
4394
  </mat-tab>
4032
4395
  </mat-tab-group>
@@ -4054,7 +4417,7 @@ class PraxisTabs {
4054
4417
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
4055
4418
  </button>
4056
4419
  </div>
4057
- `, 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: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.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$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.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: i10.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: i10.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"] }, { 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", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4420
+ `, 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: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.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$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.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: i10.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: i10.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"] }, { 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", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4058
4421
  }
4059
4422
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, decorators: [{
4060
4423
  type: Component,
@@ -4195,16 +4558,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4195
4558
  <ng-container
4196
4559
  *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
4197
4560
  >
4198
- <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
4199
- <ng-container *ngIf="l.content && form">
4561
+ <ng-container *ngIf="(l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex()); else emptyNav">
4562
+ <ng-container *ngIf="l.content">
4200
4563
  <ng-container
4201
4564
  dynamicFieldLoader
4202
4565
  [fields]="l.content || []"
4203
- [formGroup]="form!"
4566
+ [formGroup]="effectiveContentForm()"
4204
4567
  ></ng-container>
4205
4568
  </ng-container>
4206
- <ng-container *ngIf="l.widgets?.length">
4207
- <ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
4569
+ <ng-container *ngIf="safeWidgetDefinitions(l.widgets).length">
4570
+ <ng-container *ngFor="let w of safeWidgetDefinitions(l.widgets); let wi = index; trackBy: trackWidgetDefinition">
4208
4571
  <ng-container
4209
4572
  [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
4210
4573
  [context]="context || {}"
@@ -4299,34 +4662,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4299
4662
  </ng-container>
4300
4663
  </ng-template>
4301
4664
 
4302
- <ng-template matTabContent>
4303
- <ng-container *ngIf="(entry.tab.content?.length || entry.tab.widgets?.length) && groupContentReady(entry.index); else emptyTab">
4304
- <ng-container *ngIf="entry.tab.content && form">
4665
+ <ng-container *ngIf="(entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index); else emptyTab">
4666
+ <ng-container *ngIf="entry.tab.content">
4667
+ <ng-container
4668
+ dynamicFieldLoader
4669
+ [fields]="entry.tab.content || []"
4670
+ [formGroup]="effectiveContentForm()"
4671
+ ></ng-container>
4672
+ </ng-container>
4673
+ <ng-container *ngIf="safeWidgetDefinitions(entry.tab.widgets).length">
4674
+ <ng-container *ngFor="let w of safeWidgetDefinitions(entry.tab.widgets); let wi = index; trackBy: trackWidgetDefinition">
4305
4675
  <ng-container
4306
- dynamicFieldLoader
4307
- [fields]="entry.tab.content || []"
4308
- [formGroup]="form!"
4676
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
4677
+ [context]="context || {}"
4678
+ (widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
4309
4679
  ></ng-container>
4310
4680
  </ng-container>
4311
- <ng-container *ngIf="entry.tab.widgets?.length">
4312
- <ng-container *ngFor="let w of entry.tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
4313
- <ng-container
4314
- [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
4315
- [context]="context || {}"
4316
- (widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
4317
- ></ng-container>
4318
- </ng-container>
4319
- </ng-container>
4320
4681
  </ng-container>
4321
- <ng-template #emptyTab>
4322
- <praxis-empty-state-card
4323
- [inline]="true"
4324
- icon="dashboard_customize"
4325
- [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
4326
- [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
4327
- [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4328
- ></praxis-empty-state-card>
4329
- </ng-template>
4682
+ </ng-container>
4683
+ <ng-template #emptyTab>
4684
+ <praxis-empty-state-card
4685
+ [inline]="true"
4686
+ icon="dashboard_customize"
4687
+ [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
4688
+ [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
4689
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
4690
+ ></praxis-empty-state-card>
4330
4691
  </ng-template>
4331
4692
  </mat-tab>
4332
4693
  </mat-tab-group>
@@ -4382,6 +4743,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4382
4743
  type: Output
4383
4744
  }], selectFocusedIndex: [{
4384
4745
  type: Output
4746
+ }], configChange: [{
4747
+ type: Output
4385
4748
  }], widgetEvent: [{
4386
4749
  type: Output
4387
4750
  }] } });
@@ -4542,6 +4905,20 @@ const PRAXIS_TABS_PORTS = [
4542
4905
  description: 'Evento canonico emitido quando a selecao de aba muda.',
4543
4906
  exposure: { public: true, group: 'events' },
4544
4907
  },
4908
+ {
4909
+ id: 'configChange',
4910
+ label: 'Configuracao alterada',
4911
+ direction: 'output',
4912
+ semanticKind: 'event',
4913
+ schema: {
4914
+ id: 'TabsConfigChangeEvent',
4915
+ kind: 'ts-type',
4916
+ ref: 'TabsConfigChangeEvent',
4917
+ },
4918
+ cardinality: 'stream',
4919
+ description: 'Evento canonico emitido quando autoria assistida altera TabsMetadata; carrega inputPatch.config para persistencia pelo host.',
4920
+ exposure: { public: true, group: 'config' },
4921
+ },
4545
4922
  {
4546
4923
  id: 'widgetEvent',
4547
4924
  label: 'Evento interno de widget',
@@ -4614,6 +4991,12 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
4614
4991
  ],
4615
4992
  outputs: [
4616
4993
  { name: 'selectedIndexChange', type: 'number', label: 'Índice selecionado' },
4994
+ {
4995
+ name: 'configChange',
4996
+ type: 'TabsConfigChangeEvent',
4997
+ label: 'Configuração alterada',
4998
+ description: 'Emite inputPatch.config quando uma autoria assistida altera TabsMetadata.',
4999
+ },
4617
5000
  { name: 'selectedTabChange', type: 'MatTabChangeEvent', label: 'Troca de aba' },
4618
5001
  { name: 'animationDone', type: 'void' },
4619
5002
  { name: 'focusChange', type: 'MatTabChangeEvent', label: 'Foco alterado' },
@@ -5039,11 +5422,14 @@ const PRAXIS_TABS_AUTHORING_MANIFEST = {
5039
5422
  ],
5040
5423
  examples: [
5041
5424
  { id: 'add-overview-tab', request: 'Add an Overview tab before the details tab.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Overview' }, isPositive: true },
5425
+ { id: 'add-list-to-current-tab', request: 'Create a list widget inside the current training tab.', operationId: 'tab.content.set', target: 'training', params: { widgets: [{ id: 'training-list', component: 'praxis-list', title: 'Training list' }] }, isPositive: true },
5426
+ { id: 'add-form-fields-to-existing-tab', request: 'Add name, date and status fields to the existing onboarding tab.', operationId: 'tab.content.set', target: 'onboarding', params: { content: [{ name: 'name', label: 'Name', controlType: 'text' }, { name: 'plannedDate', label: 'Planned date', controlType: 'date' }, { name: 'status', label: 'Status', controlType: 'select' }] }, isPositive: true },
5042
5427
  { id: 'rename-tab', request: 'Rename the details tab to Account Details.', operationId: 'tab.label.set', target: 'details', params: { textLabel: 'Account Details' }, isPositive: true },
5043
5428
  { id: 'reorder-tabs', request: 'Move billing before overview.', operationId: 'tab.order.set', target: 'billing', params: { beforeTabId: 'overview' }, isPositive: true },
5044
5429
  { id: 'disable-tab', request: 'Disable the audit tab until the user has permission.', operationId: 'tab.disabled.set', target: 'audit', params: { disabled: true }, isPositive: true },
5045
5430
  { id: 'activate-tab', request: 'Open the documents tab by default.', operationId: 'tab.active.set', target: 'documents', params: { tabId: 'documents', selectedIndex: 2 }, isPositive: true },
5046
5431
  { id: 'reject-duplicate-tab-id', request: 'Add another tab with id overview.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Duplicate Overview' }, isPositive: false },
5432
+ { id: 'reject-current-tab-content-as-tab-add', request: 'Create a list in this tab; do not add a new tab.', operationId: 'tab.add', params: { id: 'list-in-this-tab', textLabel: 'List in this tab' }, isPositive: false },
5047
5433
  { id: 'confirm-remove-content-tab', request: 'Remove the details tab that contains widgets.', operationId: 'tab.remove', target: 'details', params: { replacementActiveTabId: 'overview' }, isPositive: true },
5048
5434
  ],
5049
5435
  };
package/index.d.ts CHANGED
@@ -4,16 +4,20 @@ import { MatTabChangeEvent } from '@angular/material/tabs';
4
4
  import { AiCapability, WidgetDefinition, WidgetEventEnvelope, WidgetEventPathSegment, PraxisI18nConfig, ComponentDocMeta, ComponentMetadataRegistry, SettingsValueProvider as SettingsValueProvider$1, AiCapabilityCategory, AiValueKind, AiCapabilityCatalog, ComponentAuthoringManifest } from '@praxisui/core';
5
5
  import { FormGroup } from '@angular/forms';
6
6
  import { CdkDragDrop } from '@angular/cdk/drag-drop';
7
- import { BaseAiAdapter, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage } from '@praxisui/ai';
7
+ import { BaseAiAdapter, AiResponseCompileResult, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage } from '@praxisui/ai';
8
8
  import { BehaviorSubject } from 'rxjs';
9
9
  import { SettingsValueProvider } from '@praxisui/settings-panel';
10
10
 
11
+ type TabsAiSummaryParams = Record<string, string | number | boolean | null | undefined>;
12
+ type TabsAiTranslate = (key: string, fallback: string, params?: TabsAiSummaryParams) => string;
11
13
  declare class TabsAiAdapter extends BaseAiAdapter<TabsMetadata> {
12
14
  private tabs;
15
+ private readonly translate?;
13
16
  componentName: string;
14
17
  componentId: string;
15
18
  componentType: string;
16
- constructor(tabs: PraxisTabs);
19
+ constructor(tabs: PraxisTabs, translate?: TabsAiTranslate | undefined);
20
+ compileAiResponse(response: Record<string, unknown>): AiResponseCompileResult | null;
17
21
  getCurrentConfig(): TabsMetadata;
18
22
  getCapabilities(): AiCapability[];
19
23
  getDataProfile(): Record<string, any>;
@@ -25,8 +29,19 @@ declare class TabsAiAdapter extends BaseAiAdapter<TabsMetadata> {
25
29
  applyPatch(patch: Partial<TabsMetadata>): Promise<PatchResult>;
26
30
  private applyConfig;
27
31
  private smartMergeTabsConfig;
32
+ private coalesceByKey;
28
33
  private mergeByKey;
29
34
  private cloneConfig;
35
+ private resolveTargetId;
36
+ private resolveTextLabel;
37
+ private resolveTabAddInput;
38
+ private resolveTabContentInput;
39
+ private summarizeOperations;
40
+ private t;
41
+ private interpolate;
42
+ private toRecord;
43
+ private toText;
44
+ private normalizeToken;
30
45
  }
31
46
 
32
47
  interface TabsMetadata {
@@ -39,6 +54,11 @@ interface TabsMetadata {
39
54
  tabs?: TabMetadata[];
40
55
  nav?: TabNavMetadata;
41
56
  }
57
+ interface TabsConfigChangeEvent {
58
+ inputPatch: {
59
+ config: TabsMetadata;
60
+ };
61
+ }
42
62
  interface TabsAppearanceConfig {
43
63
  density?: 'compact' | 'comfortable' | 'spacious';
44
64
  themeClass?: string;
@@ -151,6 +171,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
151
171
  private readonly route;
152
172
  private readonly aiAssistantSessionEffect;
153
173
  private warnedMissingId;
174
+ private loadedStorageKey;
154
175
  config: TabsMetadata | null;
155
176
  tabsId: string;
156
177
  componentInstanceId?: string;
@@ -164,6 +185,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
164
185
  selectedTabChange: EventEmitter<MatTabChangeEvent>;
165
186
  indexFocused: EventEmitter<number>;
166
187
  selectFocusedIndex: EventEmitter<number>;
188
+ configChange: EventEmitter<TabsConfigChangeEvent>;
167
189
  widgetEvent: EventEmitter<WidgetEventEnvelope>;
168
190
  aiAdapter: TabsAiAdapter;
169
191
  aiAssistantOpen: boolean;
@@ -180,6 +202,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
180
202
  private controlledSelectedIndex?;
181
203
  private readonly destroy$;
182
204
  private readonly widgetDefinitionCache;
205
+ private readonly generatedContentForm;
183
206
  ngOnInit(): void;
184
207
  ngOnChanges(changes: SimpleChanges): void;
185
208
  ngOnDestroy(): void;
@@ -196,6 +219,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
196
219
  }>;
197
220
  protected selectedVisibleNavIndex(): number;
198
221
  protected selectedVisibleTabIndex(): number;
222
+ protected effectiveContentForm(): FormGroup;
199
223
  protected onVisibleTabIndexChange(index: number): void;
200
224
  onNavClick(i: number): void;
201
225
  onNavDrop(event: CdkDragDrop<any>): void;
@@ -239,6 +263,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
239
263
  t(key: string, fallback: string): string;
240
264
  private applyTabsAuthoringPlan;
241
265
  private storageKey;
266
+ private loadStoredConfigForCurrentIdentity;
242
267
  private syncSelectionFromConfig;
243
268
  private pruneLoadedIndexes;
244
269
  private persistConfig;
@@ -261,8 +286,15 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
261
286
  protected trackNavLink(index: number, link: TabLinkMetadata): string;
262
287
  protected trackTab(index: number, tab: TabMetadata): string;
263
288
  protected trackWidgetDefinition(index: number, widget: WidgetDefinition): string;
289
+ protected safeWidgetDefinitions(widgets: unknown): WidgetDefinition[];
264
290
  protected resolveWidgetDefinition(widget: WidgetDefinition): WidgetDefinition;
291
+ private syncGeneratedContentForm;
292
+ private collectContentFields;
293
+ private resolveContentFieldName;
265
294
  protected emitWidgetEvent(path: WidgetEventPathSegment[], ev: WidgetEventEnvelope): void;
295
+ private emitAgenticPageCompositionRequest;
296
+ private shouldDelegateAiAssistantPromptToPageBuilder;
297
+ private normalizeAssistantPrompt;
266
298
  protected tabEventPath(tabId: string | undefined, tabIndex: number): WidgetEventPathSegment[];
267
299
  protected linkEventPath(linkId: string | undefined, linkIndex: number): WidgetEventPathSegment[];
268
300
  protected styleScopeId(): string;
@@ -270,8 +302,9 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
270
302
  private sanitizeCssValue;
271
303
  protected styleCss(): string | null;
272
304
  private cloneWidgetDefinition;
305
+ private cloneTabsMetadata;
273
306
  static ɵfac: i0.ɵɵFactoryDeclaration<PraxisTabs, never>;
274
- static ɵcmp: i0.ɵɵComponentDeclaration<PraxisTabs, "praxis-tabs", never, { "config": { "alias": "config"; "required": false; }; "tabsId": { "alias": "tabsId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "selectedIndex": { "alias": "selectedIndex"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "form": { "alias": "form"; "required": false; }; "context": { "alias": "context"; "required": false; }; }, { "animationDone": "animationDone"; "focusChange": "focusChange"; "selectedIndexChange": "selectedIndexChange"; "selectedTabChange": "selectedTabChange"; "indexFocused": "indexFocused"; "selectFocusedIndex": "selectFocusedIndex"; "widgetEvent": "widgetEvent"; }, never, never, true, never>;
307
+ static ɵcmp: i0.ɵɵComponentDeclaration<PraxisTabs, "praxis-tabs", never, { "config": { "alias": "config"; "required": false; }; "tabsId": { "alias": "tabsId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "selectedIndex": { "alias": "selectedIndex"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "form": { "alias": "form"; "required": false; }; "context": { "alias": "context"; "required": false; }; }, { "animationDone": "animationDone"; "focusChange": "focusChange"; "selectedIndexChange": "selectedIndexChange"; "selectedTabChange": "selectedTabChange"; "indexFocused": "indexFocused"; "selectFocusedIndex": "selectFocusedIndex"; "configChange": "configChange"; "widgetEvent": "widgetEvent"; }, never, never, true, never>;
275
308
  }
276
309
 
277
310
  declare const PRAXIS_TABS_I18N_NAMESPACE = "praxisTabs";
@@ -525,4 +558,4 @@ declare const TABS_AI_CAPABILITIES: CapabilityCatalog;
525
558
  declare const PRAXIS_TABS_AUTHORING_MANIFEST: ComponentAuthoringManifest;
526
559
 
527
560
  export { PRAXIS_TABS_AUTHORING_MANIFEST, PRAXIS_TABS_COMPONENT_METADATA, PRAXIS_TABS_I18N_CONFIG, PRAXIS_TABS_I18N_NAMESPACE, PraxisTabs, PraxisTabsConfigEditor, PraxisTabsWidgetConfigEditor, TABS_AI_CAPABILITIES, buildTabsApplyPlan, createPraxisTabsI18nConfig, createTabsAuthoringDocument, normalizeTabsAuthoringDocument, providePraxisTabsI18n, providePraxisTabsMetadata, serializeTabsAuthoringDocument, toCanonicalTabsConfig, validateTabsAuthoringDocument };
528
- export type { Capability, CapabilityCatalog, CapabilityCategory, PraxisTabsWidgetEditorInputs, PraxisTabsWidgetEditorValue, TabGroupMetadata, TabLinkMetadata, TabMetadata, TabNavMetadata, TabsAccessibilityConfig, TabsAppearanceConfig, TabsApplyPlan, TabsAuthoringBindings, TabsAuthoringDocument, TabsBehaviorConfig, TabsBindingsDiff, TabsEditorDiagnostic, TabsEditorDiagnosticLevel, TabsEditorDocumentKind, TabsEventConfig, TabsMetadata, TabsRuntimeContext, TabsRuntimePlan, TabsStyleTokens, ValueKind };
561
+ export type { Capability, CapabilityCatalog, CapabilityCategory, PraxisTabsWidgetEditorInputs, PraxisTabsWidgetEditorValue, TabGroupMetadata, TabLinkMetadata, TabMetadata, TabNavMetadata, TabsAccessibilityConfig, TabsAppearanceConfig, TabsApplyPlan, TabsAuthoringBindings, TabsAuthoringDocument, TabsBehaviorConfig, TabsBindingsDiff, TabsConfigChangeEvent, TabsEditorDiagnostic, TabsEditorDiagnosticLevel, TabsEditorDocumentKind, TabsEventConfig, TabsMetadata, TabsRuntimeContext, TabsRuntimePlan, TabsStyleTokens, ValueKind };
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@praxisui/tabs",
3
- "version": "8.0.0-beta.25",
3
+ "version": "8.0.0-beta.27",
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": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
8
  "@angular/material": "^20.0.0",
9
9
  "@angular/cdk": "^20.0.0",
10
- "@praxisui/core": "^8.0.0-beta.25",
11
- "@praxisui/dynamic-fields": "^8.0.0-beta.25",
12
- "@praxisui/settings-panel": "^8.0.0-beta.25",
10
+ "@praxisui/core": "^8.0.0-beta.27",
11
+ "@praxisui/dynamic-fields": "^8.0.0-beta.27",
12
+ "@praxisui/settings-panel": "^8.0.0-beta.27",
13
13
  "@angular/forms": "^20.0.0",
14
14
  "@angular/router": "^20.0.0",
15
- "@praxisui/ai": "^8.0.0-beta.25",
15
+ "@praxisui/ai": "^8.0.0-beta.27",
16
16
  "rxjs": "~7.8.0"
17
17
  },
18
18
  "dependencies": {
@@ -175,6 +175,7 @@ Este arquivo foi adaptado para o padrao canonico atual sem remover conteudo tecn
175
175
  | `selectedTabChange` | `MatTabChangeEvent` | Troca de aba no `mat-tab-group` | Partial | Preservado da documentação anterior. |
176
176
  | `indexFocused` | `number` | Evento nativo de foco indexado (Material) | Partial | Preservado da documentação anterior. |
177
177
  | `selectFocusedIndex` | `number` | Evento nativo de selecao por foco (Material) | Partial | Preservado da documentação anterior. |
178
+ | `configChange` | `{ inputPatch: { config: TabsMetadata } }` | Autoria assistida aplica nova configuracao de abas via adapter | Partial | Evento canonico para hosts persistirem `inputs.config` no manifesto/pagina. |
178
179
  | `widgetEvent` | `{ tabId?, tabIndex?, linkId?, linkIndex?, sourceId, output?, payload? }` | Reemissao de evento vindo de widget interno com contexto de localizacao | Partial | Preservado da documentação anterior. |
179
180
 
180
181
  ### External side channels
@@ -425,6 +426,7 @@ Notas operacionais:
425
426
  | `selectedTabChange` | `MatTabChangeEvent` | Troca de aba no `mat-tab-group` | Active |
426
427
  | `indexFocused` | `number` | Evento nativo de foco indexado (Material) | Active |
427
428
  | `selectFocusedIndex` | `number` | Evento nativo de selecao por foco (Material) | Active |
429
+ | `configChange` | `{ inputPatch: { config: TabsMetadata } }` | Autoria assistida aplica nova configuracao de abas via adapter | Active |
428
430
  | `widgetEvent` | `{ tabId?, tabIndex?, linkId?, linkIndex?, sourceId, output?, payload? }` | Reemissao de evento vindo de widget interno com contexto de localizacao | Active |
429
431
 
430
432
  #### Precedencia de modo e fluxo de render