@praxisui/tabs 8.0.0-beta.26 → 8.0.0-beta.28
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 +517 -108
- package/index.d.ts +40 -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,9 +3066,11 @@ 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;
|
|
3073
|
+
configPersistenceStrategy = 'storage-first';
|
|
2802
3074
|
set selectedIndex(index) {
|
|
2803
3075
|
if (index == null)
|
|
2804
3076
|
return;
|
|
@@ -2814,8 +3086,9 @@ class PraxisTabs {
|
|
|
2814
3086
|
selectedTabChange = new EventEmitter();
|
|
2815
3087
|
indexFocused = new EventEmitter();
|
|
2816
3088
|
selectFocusedIndex = new EventEmitter();
|
|
3089
|
+
configChange = new EventEmitter();
|
|
2817
3090
|
widgetEvent = new EventEmitter();
|
|
2818
|
-
aiAdapter = new TabsAiAdapter(this);
|
|
3091
|
+
aiAdapter = new TabsAiAdapter(this, (key, fallback, params) => this.i18n.t(key, params, fallback, PRAXIS_TABS_I18N_NAMESPACE));
|
|
2819
3092
|
aiAssistantOpen = false;
|
|
2820
3093
|
aiAssistantPrompt = '';
|
|
2821
3094
|
aiAssistantViewState = null;
|
|
@@ -2839,25 +3112,21 @@ class PraxisTabs {
|
|
|
2839
3112
|
controlledSelectedIndex;
|
|
2840
3113
|
destroy$ = new Subject();
|
|
2841
3114
|
widgetDefinitionCache = new WeakMap();
|
|
3115
|
+
generatedContentForm = new FormGroup({});
|
|
2842
3116
|
ngOnInit() {
|
|
2843
3117
|
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
|
-
}
|
|
3118
|
+
this.syncGeneratedContentForm();
|
|
3119
|
+
this.loadStoredConfigForCurrentIdentity({ warnMissingId: false });
|
|
2855
3120
|
}
|
|
2856
3121
|
ngOnChanges(changes) {
|
|
3122
|
+
if (changes['tabsId'] || changes['componentInstanceId'] || changes['configPersistenceStrategy']) {
|
|
3123
|
+
this.loadStoredConfigForCurrentIdentity({ warnMissingId: false });
|
|
3124
|
+
}
|
|
2857
3125
|
if (changes['config'] && this.config) {
|
|
2858
3126
|
// Reset loaded caches on config change and seed with current selections
|
|
2859
3127
|
this.syncSelectionFromConfig();
|
|
2860
|
-
|
|
3128
|
+
this.syncGeneratedContentForm();
|
|
3129
|
+
// Persist when tabsId provided and the instance is storage-owned.
|
|
2861
3130
|
this.persistConfig(this.config);
|
|
2862
3131
|
this.reapplyControlledSelectedIndex();
|
|
2863
3132
|
}
|
|
@@ -2900,6 +3169,9 @@ class PraxisTabs {
|
|
|
2900
3169
|
const index = entries.findIndex((entry) => entry.index === this.selectedIndexSignal());
|
|
2901
3170
|
return index >= 0 ? index : 0;
|
|
2902
3171
|
}
|
|
3172
|
+
effectiveContentForm() {
|
|
3173
|
+
return this.form || this.generatedContentForm;
|
|
3174
|
+
}
|
|
2903
3175
|
onVisibleTabIndexChange(index) {
|
|
2904
3176
|
const entry = this.visibleTabEntries()[index];
|
|
2905
3177
|
if (!entry)
|
|
@@ -3129,6 +3401,12 @@ class PraxisTabs {
|
|
|
3129
3401
|
this.syncAiAssistantSession();
|
|
3130
3402
|
}
|
|
3131
3403
|
onAiAssistantSubmit(prompt) {
|
|
3404
|
+
if (this.shouldDelegateAiAssistantPromptToPageBuilder(prompt)) {
|
|
3405
|
+
this.emitAgenticPageCompositionRequest(prompt);
|
|
3406
|
+
this.aiAssistantPrompt = '';
|
|
3407
|
+
this.closeAiAssistant();
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3132
3410
|
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
3133
3411
|
this.aiAssistantPrompt = '';
|
|
3134
3412
|
this.aiAssistantViewState = state;
|
|
@@ -3415,6 +3693,7 @@ class PraxisTabs {
|
|
|
3415
3693
|
draft.group.selectedIndex = (draft.tabs.length || 1) - 1;
|
|
3416
3694
|
});
|
|
3417
3695
|
this.config = next;
|
|
3696
|
+
this.syncGeneratedContentForm();
|
|
3418
3697
|
this.persistConfig(this.config);
|
|
3419
3698
|
this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
|
|
3420
3699
|
}
|
|
@@ -3476,6 +3755,7 @@ class PraxisTabs {
|
|
|
3476
3755
|
}
|
|
3477
3756
|
this.config = plan.canonicalConfig;
|
|
3478
3757
|
this.syncSelectionFromConfig();
|
|
3758
|
+
this.syncGeneratedContentForm();
|
|
3479
3759
|
if (plan.runtime.rebuildLazyState) {
|
|
3480
3760
|
this.groupLoaded.clear();
|
|
3481
3761
|
this.navLoaded.clear();
|
|
@@ -3492,10 +3772,27 @@ class PraxisTabs {
|
|
|
3492
3772
|
this.persistConfig(this.config);
|
|
3493
3773
|
}
|
|
3494
3774
|
}
|
|
3495
|
-
storageKey() {
|
|
3496
|
-
const id = this.componentKeyId();
|
|
3775
|
+
storageKey(options = {}) {
|
|
3776
|
+
const id = this.componentKeyId(options);
|
|
3497
3777
|
return id ? `tabs:${id}` : null;
|
|
3498
3778
|
}
|
|
3779
|
+
loadStoredConfigForCurrentIdentity(options = {}) {
|
|
3780
|
+
if (this.usesInputFirstConfig()) {
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
const key = this.storageKey(options);
|
|
3784
|
+
if (!key || key === this.loadedStorageKey)
|
|
3785
|
+
return;
|
|
3786
|
+
this.loadedStorageKey = key;
|
|
3787
|
+
this.storage.loadConfig(key).pipe(take(1)).subscribe((stored) => {
|
|
3788
|
+
if (stored) {
|
|
3789
|
+
this.config = stored;
|
|
3790
|
+
this.syncGeneratedContentForm();
|
|
3791
|
+
}
|
|
3792
|
+
this.syncSelectionFromConfig();
|
|
3793
|
+
this.reapplyControlledSelectedIndex();
|
|
3794
|
+
});
|
|
3795
|
+
}
|
|
3499
3796
|
syncSelectionFromConfig() {
|
|
3500
3797
|
const tabsLength = this.config?.tabs?.length ?? 0;
|
|
3501
3798
|
const linksLength = this.config?.nav?.links?.length ?? 0;
|
|
@@ -3518,12 +3815,17 @@ class PraxisTabs {
|
|
|
3518
3815
|
}
|
|
3519
3816
|
}
|
|
3520
3817
|
persistConfig(config) {
|
|
3818
|
+
if (this.usesInputFirstConfig())
|
|
3819
|
+
return;
|
|
3521
3820
|
const key = this.storageKey();
|
|
3522
3821
|
if (!key || !config)
|
|
3523
3822
|
return;
|
|
3524
3823
|
this.storage.saveConfig(key, config).pipe(take(1)).subscribe({ error: () => { } });
|
|
3525
3824
|
}
|
|
3526
|
-
|
|
3825
|
+
usesInputFirstConfig() {
|
|
3826
|
+
return this.configPersistenceStrategy === 'input-first' && !!this.config;
|
|
3827
|
+
}
|
|
3828
|
+
componentKeyId(options = {}) {
|
|
3527
3829
|
const key = this.componentKeys.buildComponentId({
|
|
3528
3830
|
componentType: 'praxis-tabs',
|
|
3529
3831
|
componentId: this.tabsId,
|
|
@@ -3532,7 +3834,7 @@ class PraxisTabs {
|
|
|
3532
3834
|
route: this.route,
|
|
3533
3835
|
requireComponentId: true,
|
|
3534
3836
|
});
|
|
3535
|
-
if (!key)
|
|
3837
|
+
if (!key && options.warnMissingId !== false)
|
|
3536
3838
|
this.warnMissingId();
|
|
3537
3839
|
return key;
|
|
3538
3840
|
}
|
|
@@ -3562,7 +3864,9 @@ class PraxisTabs {
|
|
|
3562
3864
|
applyConfigFromAdapter(next) {
|
|
3563
3865
|
this.config = next;
|
|
3564
3866
|
this.syncSelectionFromConfig();
|
|
3867
|
+
this.syncGeneratedContentForm();
|
|
3565
3868
|
this.persistConfig(this.config);
|
|
3869
|
+
this.configChange.emit({ inputPatch: { config: this.cloneTabsMetadata(this.config) } });
|
|
3566
3870
|
}
|
|
3567
3871
|
// =====================
|
|
3568
3872
|
// Lazy load helpers
|
|
@@ -3596,6 +3900,12 @@ class PraxisTabs {
|
|
|
3596
3900
|
trackWidgetDefinition(index, widget) {
|
|
3597
3901
|
return widget.childWidgetKey || widget.id || `widget:${index}`;
|
|
3598
3902
|
}
|
|
3903
|
+
safeWidgetDefinitions(widgets) {
|
|
3904
|
+
if (!Array.isArray(widgets)) {
|
|
3905
|
+
return [];
|
|
3906
|
+
}
|
|
3907
|
+
return widgets.filter((widget) => (!!widget && typeof widget === 'object'));
|
|
3908
|
+
}
|
|
3599
3909
|
resolveWidgetDefinition(widget) {
|
|
3600
3910
|
const cached = this.widgetDefinitionCache.get(widget);
|
|
3601
3911
|
if (cached) {
|
|
@@ -3605,12 +3915,67 @@ class PraxisTabs {
|
|
|
3605
3915
|
this.widgetDefinitionCache.set(widget, clone);
|
|
3606
3916
|
return clone;
|
|
3607
3917
|
}
|
|
3918
|
+
syncGeneratedContentForm() {
|
|
3919
|
+
if (this.form) {
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3922
|
+
for (const field of this.collectContentFields()) {
|
|
3923
|
+
const name = this.resolveContentFieldName(field);
|
|
3924
|
+
if (!name || this.generatedContentForm.contains(name)) {
|
|
3925
|
+
continue;
|
|
3926
|
+
}
|
|
3927
|
+
this.generatedContentForm.addControl(name, new FormControl(field?.defaultValue ?? null));
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
collectContentFields() {
|
|
3931
|
+
return [
|
|
3932
|
+
...((this.config?.tabs ?? []).flatMap((tab) => tab.content ?? [])),
|
|
3933
|
+
...((this.config?.nav?.links ?? []).flatMap((link) => link.content ?? [])),
|
|
3934
|
+
];
|
|
3935
|
+
}
|
|
3936
|
+
resolveContentFieldName(field) {
|
|
3937
|
+
const name = field?.name ?? field?.key ?? field?.id;
|
|
3938
|
+
return typeof name === 'string' && name.trim() ? name.trim() : null;
|
|
3939
|
+
}
|
|
3608
3940
|
emitWidgetEvent(path, ev) {
|
|
3609
3941
|
this.widgetEvent.emit({
|
|
3610
3942
|
...ev,
|
|
3611
3943
|
path: [...path, ...(ev.path || [])],
|
|
3612
3944
|
});
|
|
3613
3945
|
}
|
|
3946
|
+
emitAgenticPageCompositionRequest(prompt) {
|
|
3947
|
+
this.widgetEvent.emit({
|
|
3948
|
+
sourceComponentId: 'praxis-tabs',
|
|
3949
|
+
output: AGENTIC_PAGE_COMPOSITION_REQUEST_OUTPUT,
|
|
3950
|
+
payload: {
|
|
3951
|
+
prompt,
|
|
3952
|
+
source: 'praxis-tabs',
|
|
3953
|
+
reason: 'nested-page-composition-request',
|
|
3954
|
+
},
|
|
3955
|
+
path: [{ kind: 'tabs', id: this.tabsId }],
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
shouldDelegateAiAssistantPromptToPageBuilder(prompt) {
|
|
3959
|
+
const normalized = this.normalizeAssistantPrompt(prompt);
|
|
3960
|
+
if (!normalized)
|
|
3961
|
+
return false;
|
|
3962
|
+
const hasCompositionVerb = /\b(ajust\w*|adicion\w*|inclu\w*|cri\w*|mont\w*|complet\w*|preench\w*|use|usar|organiz\w*)\b/.test(normalized);
|
|
3963
|
+
const hasTabsContainer = /\b(abas?|tabs?|cadastro|registros?|acompanhamento|solicitacoes|workspace)\b/.test(normalized);
|
|
3964
|
+
const hasNestedWidget = /\b(formularios?|forms?|listas?|listagem|crud|cards?|tabelas?|componentes?|widgets?)\b/.test(normalized);
|
|
3965
|
+
const hasStructuredContent = /\b(campos?|colunas?|sla|historico|responsavel|prioridade|prazo|titulo|status|item)\b/.test(normalized);
|
|
3966
|
+
const hasLocalEditorialBoundary = /\b(local|editorial|fictici\w*|mock|sem api real|nao conecte api|sem conectar api|sem schema externo)\b/.test(normalized);
|
|
3967
|
+
return hasCompositionVerb
|
|
3968
|
+
&& hasTabsContainer
|
|
3969
|
+
&& (hasNestedWidget || (hasLocalEditorialBoundary && hasStructuredContent));
|
|
3970
|
+
}
|
|
3971
|
+
normalizeAssistantPrompt(prompt) {
|
|
3972
|
+
return (prompt || '')
|
|
3973
|
+
.normalize('NFD')
|
|
3974
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
3975
|
+
.toLowerCase()
|
|
3976
|
+
.replace(/\s+/g, ' ')
|
|
3977
|
+
.trim();
|
|
3978
|
+
}
|
|
3614
3979
|
tabEventPath(tabId, tabIndex) {
|
|
3615
3980
|
return [
|
|
3616
3981
|
{ kind: 'tabs', id: this.tabsId },
|
|
@@ -3770,8 +4135,17 @@ class PraxisTabs {
|
|
|
3770
4135
|
catch { }
|
|
3771
4136
|
return JSON.parse(JSON.stringify(widget));
|
|
3772
4137
|
}
|
|
4138
|
+
cloneTabsMetadata(config) {
|
|
4139
|
+
try {
|
|
4140
|
+
if (typeof structuredClone === 'function') {
|
|
4141
|
+
return structuredClone(config);
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
catch { }
|
|
4145
|
+
return JSON.parse(JSON.stringify(config));
|
|
4146
|
+
}
|
|
3773
4147
|
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: `
|
|
4148
|
+
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", configPersistenceStrategy: "configPersistenceStrategy", 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
4149
|
<div
|
|
3776
4150
|
class="praxis-tabs-root"
|
|
3777
4151
|
[class.density-compact]="config?.appearance?.density === 'compact'"
|
|
@@ -3895,16 +4269,16 @@ class PraxisTabs {
|
|
|
3895
4269
|
<ng-container
|
|
3896
4270
|
*ngIf="config?.nav?.links?.[currentNavIndex()] as l"
|
|
3897
4271
|
>
|
|
3898
|
-
<ng-container *ngIf="(l.content?.length || l.widgets
|
|
3899
|
-
<ng-container *ngIf="l.content
|
|
4272
|
+
<ng-container *ngIf="(l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex()); else emptyNav">
|
|
4273
|
+
<ng-container *ngIf="l.content">
|
|
3900
4274
|
<ng-container
|
|
3901
4275
|
dynamicFieldLoader
|
|
3902
4276
|
[fields]="l.content || []"
|
|
3903
|
-
[formGroup]="
|
|
4277
|
+
[formGroup]="effectiveContentForm()"
|
|
3904
4278
|
></ng-container>
|
|
3905
4279
|
</ng-container>
|
|
3906
|
-
<ng-container *ngIf="l.widgets
|
|
3907
|
-
<ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
4280
|
+
<ng-container *ngIf="safeWidgetDefinitions(l.widgets).length">
|
|
4281
|
+
<ng-container *ngFor="let w of safeWidgetDefinitions(l.widgets); let wi = index; trackBy: trackWidgetDefinition">
|
|
3908
4282
|
<ng-container
|
|
3909
4283
|
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
3910
4284
|
[context]="context || {}"
|
|
@@ -3999,34 +4373,32 @@ class PraxisTabs {
|
|
|
3999
4373
|
</ng-container>
|
|
4000
4374
|
</ng-template>
|
|
4001
4375
|
|
|
4002
|
-
<ng-
|
|
4003
|
-
<ng-container *ngIf="
|
|
4004
|
-
<ng-container
|
|
4376
|
+
<ng-container *ngIf="(entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index); else emptyTab">
|
|
4377
|
+
<ng-container *ngIf="entry.tab.content">
|
|
4378
|
+
<ng-container
|
|
4379
|
+
dynamicFieldLoader
|
|
4380
|
+
[fields]="entry.tab.content || []"
|
|
4381
|
+
[formGroup]="effectiveContentForm()"
|
|
4382
|
+
></ng-container>
|
|
4383
|
+
</ng-container>
|
|
4384
|
+
<ng-container *ngIf="safeWidgetDefinitions(entry.tab.widgets).length">
|
|
4385
|
+
<ng-container *ngFor="let w of safeWidgetDefinitions(entry.tab.widgets); let wi = index; trackBy: trackWidgetDefinition">
|
|
4005
4386
|
<ng-container
|
|
4006
|
-
|
|
4007
|
-
[
|
|
4008
|
-
|
|
4387
|
+
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
4388
|
+
[context]="context || {}"
|
|
4389
|
+
(widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
|
|
4009
4390
|
></ng-container>
|
|
4010
4391
|
</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
4392
|
</ng-container>
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4393
|
+
</ng-container>
|
|
4394
|
+
<ng-template #emptyTab>
|
|
4395
|
+
<praxis-empty-state-card
|
|
4396
|
+
[inline]="true"
|
|
4397
|
+
icon="dashboard_customize"
|
|
4398
|
+
[title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
|
|
4399
|
+
[description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
|
|
4400
|
+
[primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
|
|
4401
|
+
></praxis-empty-state-card>
|
|
4030
4402
|
</ng-template>
|
|
4031
4403
|
</mat-tab>
|
|
4032
4404
|
</mat-tab-group>
|
|
@@ -4054,7 +4426,7 @@ class PraxisTabs {
|
|
|
4054
4426
|
<mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
|
|
4055
4427
|
</button>
|
|
4056
4428
|
</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.
|
|
4429
|
+
`, 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", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "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
4430
|
}
|
|
4059
4431
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, decorators: [{
|
|
4060
4432
|
type: Component,
|
|
@@ -4195,16 +4567,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4195
4567
|
<ng-container
|
|
4196
4568
|
*ngIf="config?.nav?.links?.[currentNavIndex()] as l"
|
|
4197
4569
|
>
|
|
4198
|
-
<ng-container *ngIf="(l.content?.length || l.widgets
|
|
4199
|
-
<ng-container *ngIf="l.content
|
|
4570
|
+
<ng-container *ngIf="(l.content?.length || safeWidgetDefinitions(l.widgets).length) && navContentReady(currentNavIndex()); else emptyNav">
|
|
4571
|
+
<ng-container *ngIf="l.content">
|
|
4200
4572
|
<ng-container
|
|
4201
4573
|
dynamicFieldLoader
|
|
4202
4574
|
[fields]="l.content || []"
|
|
4203
|
-
[formGroup]="
|
|
4575
|
+
[formGroup]="effectiveContentForm()"
|
|
4204
4576
|
></ng-container>
|
|
4205
4577
|
</ng-container>
|
|
4206
|
-
<ng-container *ngIf="l.widgets
|
|
4207
|
-
<ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
4578
|
+
<ng-container *ngIf="safeWidgetDefinitions(l.widgets).length">
|
|
4579
|
+
<ng-container *ngFor="let w of safeWidgetDefinitions(l.widgets); let wi = index; trackBy: trackWidgetDefinition">
|
|
4208
4580
|
<ng-container
|
|
4209
4581
|
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
4210
4582
|
[context]="context || {}"
|
|
@@ -4299,34 +4671,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4299
4671
|
</ng-container>
|
|
4300
4672
|
</ng-template>
|
|
4301
4673
|
|
|
4302
|
-
<ng-
|
|
4303
|
-
<ng-container *ngIf="
|
|
4304
|
-
<ng-container
|
|
4674
|
+
<ng-container *ngIf="(entry.tab.content?.length || safeWidgetDefinitions(entry.tab.widgets).length) && groupContentReady(entry.index); else emptyTab">
|
|
4675
|
+
<ng-container *ngIf="entry.tab.content">
|
|
4676
|
+
<ng-container
|
|
4677
|
+
dynamicFieldLoader
|
|
4678
|
+
[fields]="entry.tab.content || []"
|
|
4679
|
+
[formGroup]="effectiveContentForm()"
|
|
4680
|
+
></ng-container>
|
|
4681
|
+
</ng-container>
|
|
4682
|
+
<ng-container *ngIf="safeWidgetDefinitions(entry.tab.widgets).length">
|
|
4683
|
+
<ng-container *ngFor="let w of safeWidgetDefinitions(entry.tab.widgets); let wi = index; trackBy: trackWidgetDefinition">
|
|
4305
4684
|
<ng-container
|
|
4306
|
-
|
|
4307
|
-
[
|
|
4308
|
-
|
|
4685
|
+
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
4686
|
+
[context]="context || {}"
|
|
4687
|
+
(widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
|
|
4309
4688
|
></ng-container>
|
|
4310
4689
|
</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
4690
|
</ng-container>
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4691
|
+
</ng-container>
|
|
4692
|
+
<ng-template #emptyTab>
|
|
4693
|
+
<praxis-empty-state-card
|
|
4694
|
+
[inline]="true"
|
|
4695
|
+
icon="dashboard_customize"
|
|
4696
|
+
[title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
|
|
4697
|
+
[description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
|
|
4698
|
+
[primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
|
|
4699
|
+
></praxis-empty-state-card>
|
|
4330
4700
|
</ng-template>
|
|
4331
4701
|
</mat-tab>
|
|
4332
4702
|
</mat-tab-group>
|
|
@@ -4362,6 +4732,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4362
4732
|
args: [{ required: true }]
|
|
4363
4733
|
}], componentInstanceId: [{
|
|
4364
4734
|
type: Input
|
|
4735
|
+
}], configPersistenceStrategy: [{
|
|
4736
|
+
type: Input
|
|
4365
4737
|
}], selectedIndex: [{
|
|
4366
4738
|
type: Input
|
|
4367
4739
|
}], enableCustomization: [{
|
|
@@ -4382,6 +4754,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4382
4754
|
type: Output
|
|
4383
4755
|
}], selectFocusedIndex: [{
|
|
4384
4756
|
type: Output
|
|
4757
|
+
}], configChange: [{
|
|
4758
|
+
type: Output
|
|
4385
4759
|
}], widgetEvent: [{
|
|
4386
4760
|
type: Output
|
|
4387
4761
|
}] } });
|
|
@@ -4542,6 +4916,20 @@ const PRAXIS_TABS_PORTS = [
|
|
|
4542
4916
|
description: 'Evento canonico emitido quando a selecao de aba muda.',
|
|
4543
4917
|
exposure: { public: true, group: 'events' },
|
|
4544
4918
|
},
|
|
4919
|
+
{
|
|
4920
|
+
id: 'configChange',
|
|
4921
|
+
label: 'Configuracao alterada',
|
|
4922
|
+
direction: 'output',
|
|
4923
|
+
semanticKind: 'event',
|
|
4924
|
+
schema: {
|
|
4925
|
+
id: 'TabsConfigChangeEvent',
|
|
4926
|
+
kind: 'ts-type',
|
|
4927
|
+
ref: 'TabsConfigChangeEvent',
|
|
4928
|
+
},
|
|
4929
|
+
cardinality: 'stream',
|
|
4930
|
+
description: 'Evento canonico emitido quando autoria assistida altera TabsMetadata; carrega inputPatch.config para persistencia pelo host.',
|
|
4931
|
+
exposure: { public: true, group: 'config' },
|
|
4932
|
+
},
|
|
4545
4933
|
{
|
|
4546
4934
|
id: 'widgetEvent',
|
|
4547
4935
|
label: 'Evento interno de widget',
|
|
@@ -4586,6 +4974,13 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
|
|
|
4586
4974
|
label: 'ID da instância',
|
|
4587
4975
|
description: 'Identificador opcional para múltiplas instâncias na mesma rota',
|
|
4588
4976
|
},
|
|
4977
|
+
{
|
|
4978
|
+
name: 'configPersistenceStrategy',
|
|
4979
|
+
type: '"storage-first" | "input-first"',
|
|
4980
|
+
default: 'storage-first',
|
|
4981
|
+
label: 'Estratégia de persistência',
|
|
4982
|
+
description: 'Define se a configuração persistida ou a configuração de entrada governa a instância.',
|
|
4983
|
+
},
|
|
4589
4984
|
{
|
|
4590
4985
|
name: 'selectedIndex',
|
|
4591
4986
|
type: 'number',
|
|
@@ -4614,6 +5009,12 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
|
|
|
4614
5009
|
],
|
|
4615
5010
|
outputs: [
|
|
4616
5011
|
{ name: 'selectedIndexChange', type: 'number', label: 'Índice selecionado' },
|
|
5012
|
+
{
|
|
5013
|
+
name: 'configChange',
|
|
5014
|
+
type: 'TabsConfigChangeEvent',
|
|
5015
|
+
label: 'Configuração alterada',
|
|
5016
|
+
description: 'Emite inputPatch.config quando uma autoria assistida altera TabsMetadata.',
|
|
5017
|
+
},
|
|
4617
5018
|
{ name: 'selectedTabChange', type: 'MatTabChangeEvent', label: 'Troca de aba' },
|
|
4618
5019
|
{ name: 'animationDone', type: 'void' },
|
|
4619
5020
|
{ name: 'focusChange', type: 'MatTabChangeEvent', label: 'Foco alterado' },
|
|
@@ -4749,6 +5150,11 @@ const PRAXIS_TABS_AUTHORING_MANIFEST = {
|
|
|
4749
5150
|
{ name: 'config', type: 'TabsMetadata', description: 'Canonical tabs/nav configuration.' },
|
|
4750
5151
|
{ name: 'tabsId', type: 'string', description: 'Stable id used to derive persistence scope.' },
|
|
4751
5152
|
{ name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
|
|
5153
|
+
{
|
|
5154
|
+
name: 'configPersistenceStrategy',
|
|
5155
|
+
type: '"storage-first" | "input-first"',
|
|
5156
|
+
description: 'Controls whether persisted customization or explicit input config governs the runtime. Governed previews with nested widgets should use input-first.',
|
|
5157
|
+
},
|
|
4752
5158
|
{ name: 'form', type: 'FormGroup', description: 'FormGroup consumed by dynamic field content.' },
|
|
4753
5159
|
{ name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
|
|
4754
5160
|
{ name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
|
|
@@ -5039,11 +5445,14 @@ const PRAXIS_TABS_AUTHORING_MANIFEST = {
|
|
|
5039
5445
|
],
|
|
5040
5446
|
examples: [
|
|
5041
5447
|
{ id: 'add-overview-tab', request: 'Add an Overview tab before the details tab.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Overview' }, isPositive: true },
|
|
5448
|
+
{ 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 },
|
|
5449
|
+
{ 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
5450
|
{ id: 'rename-tab', request: 'Rename the details tab to Account Details.', operationId: 'tab.label.set', target: 'details', params: { textLabel: 'Account Details' }, isPositive: true },
|
|
5043
5451
|
{ id: 'reorder-tabs', request: 'Move billing before overview.', operationId: 'tab.order.set', target: 'billing', params: { beforeTabId: 'overview' }, isPositive: true },
|
|
5044
5452
|
{ 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
5453
|
{ id: 'activate-tab', request: 'Open the documents tab by default.', operationId: 'tab.active.set', target: 'documents', params: { tabId: 'documents', selectedIndex: 2 }, isPositive: true },
|
|
5046
5454
|
{ id: 'reject-duplicate-tab-id', request: 'Add another tab with id overview.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Duplicate Overview' }, isPositive: false },
|
|
5455
|
+
{ 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
5456
|
{ id: 'confirm-remove-content-tab', request: 'Remove the details tab that contains widgets.', operationId: 'tab.remove', target: 'details', params: { replacementActiveTabId: 'overview' }, isPositive: true },
|
|
5048
5457
|
],
|
|
5049
5458
|
};
|
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,12 @@ interface TabsMetadata {
|
|
|
39
54
|
tabs?: TabMetadata[];
|
|
40
55
|
nav?: TabNavMetadata;
|
|
41
56
|
}
|
|
57
|
+
interface TabsConfigChangeEvent {
|
|
58
|
+
inputPatch: {
|
|
59
|
+
config: TabsMetadata;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
type TabsConfigPersistenceStrategy = 'storage-first' | 'input-first';
|
|
42
63
|
interface TabsAppearanceConfig {
|
|
43
64
|
density?: 'compact' | 'comfortable' | 'spacious';
|
|
44
65
|
themeClass?: string;
|
|
@@ -151,9 +172,11 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
151
172
|
private readonly route;
|
|
152
173
|
private readonly aiAssistantSessionEffect;
|
|
153
174
|
private warnedMissingId;
|
|
175
|
+
private loadedStorageKey;
|
|
154
176
|
config: TabsMetadata | null;
|
|
155
177
|
tabsId: string;
|
|
156
178
|
componentInstanceId?: string;
|
|
179
|
+
configPersistenceStrategy: TabsConfigPersistenceStrategy;
|
|
157
180
|
set selectedIndex(index: number | null | undefined);
|
|
158
181
|
enableCustomization: boolean;
|
|
159
182
|
form: FormGroup | null;
|
|
@@ -164,6 +187,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
164
187
|
selectedTabChange: EventEmitter<MatTabChangeEvent>;
|
|
165
188
|
indexFocused: EventEmitter<number>;
|
|
166
189
|
selectFocusedIndex: EventEmitter<number>;
|
|
190
|
+
configChange: EventEmitter<TabsConfigChangeEvent>;
|
|
167
191
|
widgetEvent: EventEmitter<WidgetEventEnvelope>;
|
|
168
192
|
aiAdapter: TabsAiAdapter;
|
|
169
193
|
aiAssistantOpen: boolean;
|
|
@@ -180,6 +204,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
180
204
|
private controlledSelectedIndex?;
|
|
181
205
|
private readonly destroy$;
|
|
182
206
|
private readonly widgetDefinitionCache;
|
|
207
|
+
private readonly generatedContentForm;
|
|
183
208
|
ngOnInit(): void;
|
|
184
209
|
ngOnChanges(changes: SimpleChanges): void;
|
|
185
210
|
ngOnDestroy(): void;
|
|
@@ -196,6 +221,7 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
196
221
|
}>;
|
|
197
222
|
protected selectedVisibleNavIndex(): number;
|
|
198
223
|
protected selectedVisibleTabIndex(): number;
|
|
224
|
+
protected effectiveContentForm(): FormGroup;
|
|
199
225
|
protected onVisibleTabIndexChange(index: number): void;
|
|
200
226
|
onNavClick(i: number): void;
|
|
201
227
|
onNavDrop(event: CdkDragDrop<any>): void;
|
|
@@ -239,9 +265,11 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
239
265
|
t(key: string, fallback: string): string;
|
|
240
266
|
private applyTabsAuthoringPlan;
|
|
241
267
|
private storageKey;
|
|
268
|
+
private loadStoredConfigForCurrentIdentity;
|
|
242
269
|
private syncSelectionFromConfig;
|
|
243
270
|
private pruneLoadedIndexes;
|
|
244
271
|
private persistConfig;
|
|
272
|
+
private usesInputFirstConfig;
|
|
245
273
|
private componentKeyId;
|
|
246
274
|
private warnMissingId;
|
|
247
275
|
private clampIndex;
|
|
@@ -261,8 +289,15 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
261
289
|
protected trackNavLink(index: number, link: TabLinkMetadata): string;
|
|
262
290
|
protected trackTab(index: number, tab: TabMetadata): string;
|
|
263
291
|
protected trackWidgetDefinition(index: number, widget: WidgetDefinition): string;
|
|
292
|
+
protected safeWidgetDefinitions(widgets: unknown): WidgetDefinition[];
|
|
264
293
|
protected resolveWidgetDefinition(widget: WidgetDefinition): WidgetDefinition;
|
|
294
|
+
private syncGeneratedContentForm;
|
|
295
|
+
private collectContentFields;
|
|
296
|
+
private resolveContentFieldName;
|
|
265
297
|
protected emitWidgetEvent(path: WidgetEventPathSegment[], ev: WidgetEventEnvelope): void;
|
|
298
|
+
private emitAgenticPageCompositionRequest;
|
|
299
|
+
private shouldDelegateAiAssistantPromptToPageBuilder;
|
|
300
|
+
private normalizeAssistantPrompt;
|
|
266
301
|
protected tabEventPath(tabId: string | undefined, tabIndex: number): WidgetEventPathSegment[];
|
|
267
302
|
protected linkEventPath(linkId: string | undefined, linkIndex: number): WidgetEventPathSegment[];
|
|
268
303
|
protected styleScopeId(): string;
|
|
@@ -270,8 +305,9 @@ declare class PraxisTabs implements OnInit, OnChanges, OnDestroy {
|
|
|
270
305
|
private sanitizeCssValue;
|
|
271
306
|
protected styleCss(): string | null;
|
|
272
307
|
private cloneWidgetDefinition;
|
|
308
|
+
private cloneTabsMetadata;
|
|
273
309
|
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>;
|
|
310
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PraxisTabs, "praxis-tabs", never, { "config": { "alias": "config"; "required": false; }; "tabsId": { "alias": "tabsId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "configPersistenceStrategy": { "alias": "configPersistenceStrategy"; "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
311
|
}
|
|
276
312
|
|
|
277
313
|
declare const PRAXIS_TABS_I18N_NAMESPACE = "praxisTabs";
|
|
@@ -525,4 +561,4 @@ declare const TABS_AI_CAPABILITIES: CapabilityCatalog;
|
|
|
525
561
|
declare const PRAXIS_TABS_AUTHORING_MANIFEST: ComponentAuthoringManifest;
|
|
526
562
|
|
|
527
563
|
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 };
|
|
564
|
+
export type { Capability, CapabilityCatalog, CapabilityCategory, PraxisTabsWidgetEditorInputs, PraxisTabsWidgetEditorValue, TabGroupMetadata, TabLinkMetadata, TabMetadata, TabNavMetadata, TabsAccessibilityConfig, TabsAppearanceConfig, TabsApplyPlan, TabsAuthoringBindings, TabsAuthoringDocument, TabsBehaviorConfig, TabsBindingsDiff, TabsConfigChangeEvent, TabsConfigPersistenceStrategy, 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.28",
|
|
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.28",
|
|
11
|
+
"@praxisui/dynamic-fields": "^8.0.0-beta.28",
|
|
12
|
+
"@praxisui/settings-panel": "^8.0.0-beta.28",
|
|
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.28",
|
|
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
|