@praxisui/tabs 8.0.0-beta.26 → 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 +2 -0
- package/fesm2022/praxisui-tabs.mjs +493 -107
- package/index.d.ts +37 -4
- package/package.json +5 -5
- package/src/lib/praxis-tabs.json-api.md +2 -0
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 {
|
|
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
|
|
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
|
|
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(
|
|
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: '
|
|
2493
|
-
errorText: '
|
|
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
|
-
|
|
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
|
-
|
|
2845
|
-
|
|
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
|
|
3899
|
-
<ng-container *ngIf="l.content
|
|
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]="
|
|
4268
|
+
[formGroup]="effectiveContentForm()"
|
|
3904
4269
|
></ng-container>
|
|
3905
4270
|
</ng-container>
|
|
3906
|
-
<ng-container *ngIf="l.widgets
|
|
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-
|
|
4003
|
-
<ng-container *ngIf="
|
|
4004
|
-
<ng-container
|
|
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
|
-
|
|
4007
|
-
[
|
|
4008
|
-
|
|
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
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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.
|
|
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
|
|
4199
|
-
<ng-container *ngIf="l.content
|
|
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]="
|
|
4566
|
+
[formGroup]="effectiveContentForm()"
|
|
4204
4567
|
></ng-container>
|
|
4205
4568
|
</ng-container>
|
|
4206
|
-
<ng-container *ngIf="l.widgets
|
|
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-
|
|
4303
|
-
<ng-container *ngIf="
|
|
4304
|
-
<ng-container
|
|
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
|
-
|
|
4307
|
-
[
|
|
4308
|
-
|
|
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
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
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.
|
|
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.
|
|
11
|
-
"@praxisui/dynamic-fields": "^8.0.0-beta.
|
|
12
|
-
"@praxisui/settings-panel": "^8.0.0-beta.
|
|
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.
|
|
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
|