@praxisui/tabs 8.0.0-beta.2 → 8.0.0-beta.21
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 +85 -12
- package/fesm2022/praxisui-tabs.mjs +1471 -98
- package/index.d.ts +119 -7
- package/package.json +8 -4
- package/src/lib/praxis-tabs-config-editor.json-api.md +597 -0
- package/src/lib/praxis-tabs.json-api.md +978 -0
- package/src/lib/quick-setup/tabs-quick-setup.json-api.md +491 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Inject, Component, EventEmitter, signal, Output,
|
|
2
|
+
import { inject, Input, Inject, Component, ChangeDetectorRef, effect, EventEmitter, signal, Output, ChangeDetectionStrategy, ViewChild, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
3
3
|
import { ActivatedRoute } from '@angular/router';
|
|
4
4
|
import * as i1$1 from '@angular/common';
|
|
5
5
|
import { CommonModule } from '@angular/common';
|
|
@@ -25,11 +25,11 @@ import * as i9 from '@angular/material/slide-toggle';
|
|
|
25
25
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
26
26
|
import * as i10 from '@angular/cdk/drag-drop';
|
|
27
27
|
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
28
|
-
import { BehaviorSubject, Subject } from 'rxjs';
|
|
28
|
+
import { BehaviorSubject, firstValueFrom, Subject, Subscription } from 'rxjs';
|
|
29
29
|
import { produce } from 'immer';
|
|
30
30
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
31
31
|
import { take, takeUntil } from 'rxjs/operators';
|
|
32
|
-
import { BaseAiAdapter,
|
|
32
|
+
import { BaseAiAdapter, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
|
|
33
33
|
|
|
34
34
|
const DOCUMENT_KIND = 'praxis.tabs.editor';
|
|
35
35
|
const DOCUMENT_VERSION = 1;
|
|
@@ -533,6 +533,12 @@ function providePraxisTabsI18n() {
|
|
|
533
533
|
class PraxisTabsConfigEditor {
|
|
534
534
|
registry;
|
|
535
535
|
i18n = inject(PraxisI18nService);
|
|
536
|
+
set document(value) {
|
|
537
|
+
if (!value) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.initializeDocument(value);
|
|
541
|
+
}
|
|
536
542
|
primaryMode = 'group';
|
|
537
543
|
editedDocument;
|
|
538
544
|
editedConfig;
|
|
@@ -612,12 +618,18 @@ class PraxisTabsConfigEditor {
|
|
|
612
618
|
this.initialDocument = structuredClone(incomingDocument);
|
|
613
619
|
this.editedDocument = structuredClone(incomingDocument);
|
|
614
620
|
this.editedConfig = this.editedDocument.config;
|
|
615
|
-
this.
|
|
616
|
-
this.
|
|
621
|
+
this.initializeDocument(incomingDocument);
|
|
622
|
+
this.componentOptions = this.registry.getAll().map((m) => ({ id: m.id, friendlyName: m.friendlyName }));
|
|
623
|
+
}
|
|
624
|
+
initializeDocument(document) {
|
|
625
|
+
const normalized = normalizeTabsAuthoringDocument(document);
|
|
626
|
+
this.initialDocument = structuredClone(normalized);
|
|
627
|
+
this.syncEditorStateFromDocument(normalized);
|
|
617
628
|
this.jsonText = this.stringify(this.editedDocument);
|
|
629
|
+
this.isValid = true;
|
|
630
|
+
this.errorMsg = '';
|
|
618
631
|
this.updateDirty();
|
|
619
632
|
this.refreshDiagnostics();
|
|
620
|
-
this.componentOptions = this.registry.getAll().map((m) => ({ id: m.id, friendlyName: m.friendlyName }));
|
|
621
633
|
}
|
|
622
634
|
inferPrimaryMode(config) {
|
|
623
635
|
return config?.nav?.links?.length ? 'nav' : 'group';
|
|
@@ -784,6 +796,7 @@ class PraxisTabsConfigEditor {
|
|
|
784
796
|
this.editedConfig.tabs.push({
|
|
785
797
|
id: `tab${(this.editedConfig.tabs.length + 1)}`,
|
|
786
798
|
textLabel: this.t('defaults.newTabLabel', 'New Tab'),
|
|
799
|
+
visible: true,
|
|
787
800
|
});
|
|
788
801
|
this.onAppearanceChange();
|
|
789
802
|
}
|
|
@@ -859,6 +872,7 @@ class PraxisTabsConfigEditor {
|
|
|
859
872
|
this.nav.links.push({
|
|
860
873
|
id: `link${this.nav.links.length + 1}`,
|
|
861
874
|
label: this.t('defaults.newLinkLabel', 'New Link'),
|
|
875
|
+
visible: true,
|
|
862
876
|
});
|
|
863
877
|
this.onAppearanceChange();
|
|
864
878
|
}
|
|
@@ -953,7 +967,7 @@ class PraxisTabsConfigEditor {
|
|
|
953
967
|
this.onAppearanceChange();
|
|
954
968
|
}
|
|
955
969
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }, { token: i1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Component });
|
|
956
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
|
|
970
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", inputs: { document: "document" }, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
|
|
957
971
|
<div class="editor-shell">
|
|
958
972
|
<div class="editor-topbar">
|
|
959
973
|
<mat-form-field appearance="outline" class="editor-mode-field">
|
|
@@ -1293,6 +1307,9 @@ class PraxisTabsConfigEditor {
|
|
|
1293
1307
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
|
|
1294
1308
|
<input matInput [(ngModel)]="tab.textLabel" (ngModelChange)="onAppearanceChange()" />
|
|
1295
1309
|
</mat-form-field>
|
|
1310
|
+
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.icon', 'Icone') }}</mat-label>
|
|
1311
|
+
<input matInput [(ngModel)]="tab.icon" (ngModelChange)="onAppearanceChange()" />
|
|
1312
|
+
</mat-form-field>
|
|
1296
1313
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.labelClass', 'Classe do rotulo') }}</mat-label>
|
|
1297
1314
|
<input matInput [(ngModel)]="tab.labelClass" (ngModelChange)="onAppearanceChange()" />
|
|
1298
1315
|
</mat-form-field>
|
|
@@ -1306,7 +1323,10 @@ class PraxisTabsConfigEditor {
|
|
|
1306
1323
|
<input matInput [(ngModel)]="tab.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
|
|
1307
1324
|
</mat-form-field>
|
|
1308
1325
|
</div>
|
|
1309
|
-
<
|
|
1326
|
+
<div class="editor-row">
|
|
1327
|
+
<mat-slide-toggle [(ngModel)]="tab.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disabled', 'Desativada') }}</mat-slide-toggle>
|
|
1328
|
+
<mat-slide-toggle [ngModel]="tab.visible !== false" (ngModelChange)="tab.visible = $event; onAppearanceChange()">{{ t('editor.toggles.visible', 'Visivel') }}</mat-slide-toggle>
|
|
1329
|
+
</div>
|
|
1310
1330
|
|
|
1311
1331
|
<!-- Widgets (componentes dinâmicos) -->
|
|
1312
1332
|
<div class="editor-divider editor-grid">
|
|
@@ -1384,9 +1404,13 @@ class PraxisTabsConfigEditor {
|
|
|
1384
1404
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
|
|
1385
1405
|
<input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
|
|
1386
1406
|
</mat-form-field>
|
|
1407
|
+
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.icon', 'Icone') }}</mat-label>
|
|
1408
|
+
<input matInput [(ngModel)]="l.icon" (ngModelChange)="onAppearanceChange()" />
|
|
1409
|
+
</mat-form-field>
|
|
1387
1410
|
</div>
|
|
1388
1411
|
<div class="editor-row">
|
|
1389
1412
|
<mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.linkDisabled', 'Desativado') }}</mat-slide-toggle>
|
|
1413
|
+
<mat-slide-toggle [ngModel]="l.visible !== false" (ngModelChange)="l.visible = $event; onAppearanceChange()">{{ t('editor.toggles.visible', 'Visivel') }}</mat-slide-toggle>
|
|
1390
1414
|
<mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
|
|
1391
1415
|
<mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
|
|
1392
1416
|
</div>
|
|
@@ -1797,6 +1821,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1797
1821
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
|
|
1798
1822
|
<input matInput [(ngModel)]="tab.textLabel" (ngModelChange)="onAppearanceChange()" />
|
|
1799
1823
|
</mat-form-field>
|
|
1824
|
+
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.icon', 'Icone') }}</mat-label>
|
|
1825
|
+
<input matInput [(ngModel)]="tab.icon" (ngModelChange)="onAppearanceChange()" />
|
|
1826
|
+
</mat-form-field>
|
|
1800
1827
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.labelClass', 'Classe do rotulo') }}</mat-label>
|
|
1801
1828
|
<input matInput [(ngModel)]="tab.labelClass" (ngModelChange)="onAppearanceChange()" />
|
|
1802
1829
|
</mat-form-field>
|
|
@@ -1810,7 +1837,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1810
1837
|
<input matInput [(ngModel)]="tab.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
|
|
1811
1838
|
</mat-form-field>
|
|
1812
1839
|
</div>
|
|
1813
|
-
<
|
|
1840
|
+
<div class="editor-row">
|
|
1841
|
+
<mat-slide-toggle [(ngModel)]="tab.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disabled', 'Desativada') }}</mat-slide-toggle>
|
|
1842
|
+
<mat-slide-toggle [ngModel]="tab.visible !== false" (ngModelChange)="tab.visible = $event; onAppearanceChange()">{{ t('editor.toggles.visible', 'Visivel') }}</mat-slide-toggle>
|
|
1843
|
+
</div>
|
|
1814
1844
|
|
|
1815
1845
|
<!-- Widgets (componentes dinâmicos) -->
|
|
1816
1846
|
<div class="editor-divider editor-grid">
|
|
@@ -1888,9 +1918,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1888
1918
|
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
|
|
1889
1919
|
<input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
|
|
1890
1920
|
</mat-form-field>
|
|
1921
|
+
<mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.icon', 'Icone') }}</mat-label>
|
|
1922
|
+
<input matInput [(ngModel)]="l.icon" (ngModelChange)="onAppearanceChange()" />
|
|
1923
|
+
</mat-form-field>
|
|
1891
1924
|
</div>
|
|
1892
1925
|
<div class="editor-row">
|
|
1893
1926
|
<mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.linkDisabled', 'Desativado') }}</mat-slide-toggle>
|
|
1927
|
+
<mat-slide-toggle [ngModel]="l.visible !== false" (ngModelChange)="l.visible = $event; onAppearanceChange()">{{ t('editor.toggles.visible', 'Visivel') }}</mat-slide-toggle>
|
|
1894
1928
|
<mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
|
|
1895
1929
|
<mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
|
|
1896
1930
|
</div>
|
|
@@ -1949,7 +1983,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1949
1983
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1950
1984
|
type: Inject,
|
|
1951
1985
|
args: [SETTINGS_PANEL_DATA]
|
|
1952
|
-
}] }, { type: i1.ComponentMetadataRegistry }]
|
|
1986
|
+
}] }, { type: i1.ComponentMetadataRegistry }], propDecorators: { document: [{
|
|
1987
|
+
type: Input
|
|
1988
|
+
}] } });
|
|
1953
1989
|
|
|
1954
1990
|
class TabsQuickSetupComponent {
|
|
1955
1991
|
i18n = inject(PraxisI18nService);
|
|
@@ -2260,6 +2296,8 @@ const TABS_AI_CAPABILITIES = {
|
|
|
2260
2296
|
class TabsAiAdapter extends BaseAiAdapter {
|
|
2261
2297
|
tabs;
|
|
2262
2298
|
componentName = 'Praxis Tabs';
|
|
2299
|
+
componentId = 'praxis-tabs';
|
|
2300
|
+
componentType = 'tabs';
|
|
2263
2301
|
constructor(tabs) {
|
|
2264
2302
|
super();
|
|
2265
2303
|
this.tabs = tabs;
|
|
@@ -2270,6 +2308,49 @@ class TabsAiAdapter extends BaseAiAdapter {
|
|
|
2270
2308
|
getCapabilities() {
|
|
2271
2309
|
return TABS_AI_CAPABILITIES.capabilities;
|
|
2272
2310
|
}
|
|
2311
|
+
getDataProfile() {
|
|
2312
|
+
const cfg = this.tabs.config;
|
|
2313
|
+
return {
|
|
2314
|
+
mode: cfg?.nav?.links?.length ? 'nav' : 'group',
|
|
2315
|
+
tabCount: cfg?.tabs?.length ?? 0,
|
|
2316
|
+
linkCount: cfg?.nav?.links?.length ?? 0,
|
|
2317
|
+
widgetCount: [
|
|
2318
|
+
...(cfg?.tabs ?? []).flatMap((tab) => tab.widgets ?? []),
|
|
2319
|
+
...(cfg?.nav?.links ?? []).flatMap((link) => link.widgets ?? []),
|
|
2320
|
+
].length,
|
|
2321
|
+
fieldCount: [
|
|
2322
|
+
...(cfg?.tabs ?? []).flatMap((tab) => tab.content ?? []),
|
|
2323
|
+
...(cfg?.nav?.links ?? []).flatMap((link) => link.content ?? []),
|
|
2324
|
+
].length,
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
getSchemaFields() {
|
|
2328
|
+
const cfg = this.tabs.config;
|
|
2329
|
+
return [
|
|
2330
|
+
...(cfg?.tabs ?? []).flatMap((tab) => tab.content ?? []),
|
|
2331
|
+
...(cfg?.nav?.links ?? []).flatMap((link) => link.content ?? []),
|
|
2332
|
+
]
|
|
2333
|
+
.map((field) => field?.name || field?.key || field?.id)
|
|
2334
|
+
.filter((name) => typeof name === 'string' && !!name.trim())
|
|
2335
|
+
.map((name) => ({ name }));
|
|
2336
|
+
}
|
|
2337
|
+
getAuthoringContext() {
|
|
2338
|
+
return {
|
|
2339
|
+
authoringManifestRef: {
|
|
2340
|
+
componentId: 'praxis-tabs',
|
|
2341
|
+
source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
|
|
2342
|
+
},
|
|
2343
|
+
runtimeAuthoringPolicy: {
|
|
2344
|
+
mode: 'agentic-authoring',
|
|
2345
|
+
enableCustomization: !!this.tabs.enableCustomization,
|
|
2346
|
+
canApplyLocalPatch: false,
|
|
2347
|
+
reason: 'praxis-tabs ainda exige componentEditPlan manifest-backed antes de aplicar patch local pelo copiloto.',
|
|
2348
|
+
},
|
|
2349
|
+
domainCatalog: {
|
|
2350
|
+
recommendedAuthoringFlow: 'component_authoring',
|
|
2351
|
+
},
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2273
2354
|
getRuntimeState() {
|
|
2274
2355
|
const cfg = this.tabs.config;
|
|
2275
2356
|
return {
|
|
@@ -2355,6 +2436,340 @@ class TabsAiAdapter extends BaseAiAdapter {
|
|
|
2355
2436
|
}
|
|
2356
2437
|
}
|
|
2357
2438
|
|
|
2439
|
+
class TabsAgenticAuthoringTurnFlow {
|
|
2440
|
+
adapter;
|
|
2441
|
+
aiApi;
|
|
2442
|
+
mode = 'agentic-authoring';
|
|
2443
|
+
constructor(adapter, aiApi) {
|
|
2444
|
+
this.adapter = adapter;
|
|
2445
|
+
this.aiApi = aiApi;
|
|
2446
|
+
}
|
|
2447
|
+
async submit(request) {
|
|
2448
|
+
const prompt = (request.prompt ?? '').trim();
|
|
2449
|
+
if (!prompt) {
|
|
2450
|
+
return {
|
|
2451
|
+
state: 'listening',
|
|
2452
|
+
phase: 'capture',
|
|
2453
|
+
statusText: '',
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
const componentId = this.adapter.componentId || request.componentId || 'praxis-tabs';
|
|
2457
|
+
const componentType = this.adapter.componentType || request.componentType || 'tabs';
|
|
2458
|
+
const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
|
|
2459
|
+
const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
|
|
2460
|
+
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
2461
|
+
const schemaFields = this.adapter.getSchemaFields?.()
|
|
2462
|
+
?.map((field) => this.toAiJsonObject(field))
|
|
2463
|
+
.filter((field) => Object.keys(field).length > 0);
|
|
2464
|
+
const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
|
|
2465
|
+
if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
|
|
2466
|
+
return this.toGovernedDecisionHandoff(prompt, request);
|
|
2467
|
+
}
|
|
2468
|
+
const response = await firstValueFrom(this.aiApi.getPatch({
|
|
2469
|
+
componentId,
|
|
2470
|
+
componentType,
|
|
2471
|
+
userPrompt: prompt,
|
|
2472
|
+
sessionId: request.sessionId,
|
|
2473
|
+
clientTurnId: request.clientTurnId,
|
|
2474
|
+
messages: this.toChatMessages(request.messages, prompt),
|
|
2475
|
+
currentState,
|
|
2476
|
+
currentStateDigest: this.buildCurrentStateDigest(dataProfile),
|
|
2477
|
+
uiContextRef: {
|
|
2478
|
+
componentId,
|
|
2479
|
+
componentType,
|
|
2480
|
+
},
|
|
2481
|
+
...(dataProfile ? { dataProfile } : {}),
|
|
2482
|
+
...(runtimeState ? { runtimeState } : {}),
|
|
2483
|
+
...(schemaFields?.length ? { schemaFields } : {}),
|
|
2484
|
+
...(contextHints ? { contextHints } : {}),
|
|
2485
|
+
}));
|
|
2486
|
+
return this.toTurnResult(this.compileAdapterResponse(response), request);
|
|
2487
|
+
}
|
|
2488
|
+
async apply(_request) {
|
|
2489
|
+
return {
|
|
2490
|
+
state: 'error',
|
|
2491
|
+
phase: 'apply',
|
|
2492
|
+
assistantMessage: 'As abas ainda exigem componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
|
|
2493
|
+
errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-tabs.',
|
|
2494
|
+
canApply: false,
|
|
2495
|
+
pendingPatch: null,
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
cancel() {
|
|
2499
|
+
return Promise.resolve({
|
|
2500
|
+
state: 'listening',
|
|
2501
|
+
phase: 'capture',
|
|
2502
|
+
assistantMessage: 'Solicitacao cancelada.',
|
|
2503
|
+
statusText: '',
|
|
2504
|
+
canApply: false,
|
|
2505
|
+
pendingPatch: null,
|
|
2506
|
+
pendingClarification: null,
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
retry(request) {
|
|
2510
|
+
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
2511
|
+
.find((message) => message.role === 'user')?.text;
|
|
2512
|
+
return this.submit({
|
|
2513
|
+
...request,
|
|
2514
|
+
prompt: lastPrompt ?? request.prompt,
|
|
2515
|
+
action: { kind: 'retry' },
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
toTurnResult(response, request) {
|
|
2519
|
+
if (!response) {
|
|
2520
|
+
return {
|
|
2521
|
+
state: 'error',
|
|
2522
|
+
phase: 'capture',
|
|
2523
|
+
assistantMessage: 'Resposta vazia da IA.',
|
|
2524
|
+
errorText: 'Resposta vazia da IA.',
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
if (response.type === 'clarification') {
|
|
2528
|
+
return {
|
|
2529
|
+
state: 'clarification',
|
|
2530
|
+
phase: 'clarify',
|
|
2531
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
2532
|
+
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
2533
|
+
clarificationQuestions: this.toClarificationQuestions(response),
|
|
2534
|
+
quickReplies: this.toQuickReplies(response),
|
|
2535
|
+
canApply: false,
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
if (response.type === 'info') {
|
|
2539
|
+
const message = response.message || response.explanation || 'Informacao gerada.';
|
|
2540
|
+
return {
|
|
2541
|
+
state: 'success',
|
|
2542
|
+
phase: 'summarize',
|
|
2543
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
2544
|
+
assistantMessage: message,
|
|
2545
|
+
statusText: message,
|
|
2546
|
+
canApply: false,
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
if (response.type === 'error') {
|
|
2550
|
+
const message = response.message || 'Falha ao gerar alteracao de abas.';
|
|
2551
|
+
return {
|
|
2552
|
+
state: 'error',
|
|
2553
|
+
phase: 'capture',
|
|
2554
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
2555
|
+
assistantMessage: message,
|
|
2556
|
+
errorText: message,
|
|
2557
|
+
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
2561
|
+
return {
|
|
2562
|
+
state: 'error',
|
|
2563
|
+
phase: 'review',
|
|
2564
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
2565
|
+
assistantMessage: 'As abas rejeitaram patch livre. Gere um componentEditPlan validado pelo PRAXIS_TABS_AUTHORING_MANIFEST antes de propor alteracao local.',
|
|
2566
|
+
errorText: 'Patch livre de abas rejeitado.',
|
|
2567
|
+
canApply: false,
|
|
2568
|
+
pendingPatch: null,
|
|
2569
|
+
diagnostics: {
|
|
2570
|
+
warnings: [
|
|
2571
|
+
'free-tabs-patch-rejected',
|
|
2572
|
+
'Use componentEditPlan validado contra PRAXIS_TABS_AUTHORING_MANIFEST.',
|
|
2573
|
+
],
|
|
2574
|
+
},
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
return {
|
|
2578
|
+
state: 'success',
|
|
2579
|
+
phase: 'summarize',
|
|
2580
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
2581
|
+
assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
2582
|
+
statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
2583
|
+
canApply: false,
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
compileAdapterResponse(response) {
|
|
2587
|
+
const compiled = this.adapter.compileAiResponse?.(response);
|
|
2588
|
+
if (!compiled) {
|
|
2589
|
+
return response;
|
|
2590
|
+
}
|
|
2591
|
+
if (compiled.type === 'error') {
|
|
2592
|
+
return {
|
|
2593
|
+
type: 'error',
|
|
2594
|
+
message: compiled.message || 'O componentEditPlan das abas nao passou na validacao de capacidades.',
|
|
2595
|
+
warnings: compiled.warnings,
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
const warnings = [
|
|
2599
|
+
...(response.warnings ?? []),
|
|
2600
|
+
...(compiled.warnings ?? []),
|
|
2601
|
+
];
|
|
2602
|
+
return {
|
|
2603
|
+
...response,
|
|
2604
|
+
...compiled,
|
|
2605
|
+
patch: compiled.patch,
|
|
2606
|
+
warnings: warnings.length ? warnings : undefined,
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
toChatMessages(messages, prompt) {
|
|
2610
|
+
const supported = (messages ?? [])
|
|
2611
|
+
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
2612
|
+
.map((message) => ({
|
|
2613
|
+
role: message.role,
|
|
2614
|
+
content: message.text,
|
|
2615
|
+
}))
|
|
2616
|
+
.filter((message) => message.content.trim().length > 0);
|
|
2617
|
+
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
2618
|
+
}
|
|
2619
|
+
toClarificationQuestions(response) {
|
|
2620
|
+
const labels = response.questions?.length
|
|
2621
|
+
? response.questions
|
|
2622
|
+
: response.message
|
|
2623
|
+
? [response.message]
|
|
2624
|
+
: ['Qual ajuste voce quer aplicar nas abas?'];
|
|
2625
|
+
const options = this.toQuickReplies(response).map((reply) => ({
|
|
2626
|
+
id: reply.id,
|
|
2627
|
+
label: reply.label,
|
|
2628
|
+
value: reply.prompt,
|
|
2629
|
+
}));
|
|
2630
|
+
return labels.map((label, index) => ({
|
|
2631
|
+
id: `tabs-clarification-${index + 1}`,
|
|
2632
|
+
type: options.length ? 'single-choice' : 'text',
|
|
2633
|
+
label,
|
|
2634
|
+
allowCustom: true,
|
|
2635
|
+
options,
|
|
2636
|
+
}));
|
|
2637
|
+
}
|
|
2638
|
+
toQuickReplies(response) {
|
|
2639
|
+
const payloads = response.optionPayloads ?? [];
|
|
2640
|
+
if (payloads.length) {
|
|
2641
|
+
return payloads
|
|
2642
|
+
.map((option, index) => {
|
|
2643
|
+
const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
2644
|
+
const prompt = option.example?.trim() || option.value?.trim() || label;
|
|
2645
|
+
return {
|
|
2646
|
+
id: `option-${index + 1}`,
|
|
2647
|
+
label,
|
|
2648
|
+
prompt,
|
|
2649
|
+
kind: 'clarification-option',
|
|
2650
|
+
};
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
return (response.options ?? [])
|
|
2654
|
+
.filter((option) => !!option?.trim())
|
|
2655
|
+
.map((option, index) => ({
|
|
2656
|
+
id: `option-${index + 1}`,
|
|
2657
|
+
label: option.trim(),
|
|
2658
|
+
prompt: option.trim(),
|
|
2659
|
+
kind: 'clarification-option',
|
|
2660
|
+
}));
|
|
2661
|
+
}
|
|
2662
|
+
buildCurrentStateDigest(dataProfile) {
|
|
2663
|
+
const tabCount = typeof dataProfile?.['tabCount'] === 'number' ? dataProfile['tabCount'] : undefined;
|
|
2664
|
+
const linkCount = typeof dataProfile?.['linkCount'] === 'number' ? dataProfile['linkCount'] : undefined;
|
|
2665
|
+
const rowCount = (tabCount ?? 0) + (linkCount ?? 0);
|
|
2666
|
+
return rowCount > 0 ? { rowCount } : {};
|
|
2667
|
+
}
|
|
2668
|
+
shouldRouteToGovernedDecision(prompt, contextHints) {
|
|
2669
|
+
const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
2670
|
+
const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
|
|
2671
|
+
if (recommendedFlow === 'shared_rule_authoring')
|
|
2672
|
+
return true;
|
|
2673
|
+
return [
|
|
2674
|
+
'regra',
|
|
2675
|
+
'politica',
|
|
2676
|
+
'policy',
|
|
2677
|
+
'compliance',
|
|
2678
|
+
'lgpd',
|
|
2679
|
+
'privacidade',
|
|
2680
|
+
'aprovacao',
|
|
2681
|
+
'aprovar',
|
|
2682
|
+
'publicar',
|
|
2683
|
+
'materializar',
|
|
2684
|
+
'enforcement',
|
|
2685
|
+
'validacao de negocio',
|
|
2686
|
+
'validar negocio',
|
|
2687
|
+
'elegibilidade',
|
|
2688
|
+
'permissao',
|
|
2689
|
+
'acesso',
|
|
2690
|
+
].some((term) => normalized.includes(term));
|
|
2691
|
+
}
|
|
2692
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
2693
|
+
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.';
|
|
2694
|
+
return {
|
|
2695
|
+
state: 'clarification',
|
|
2696
|
+
phase: 'clarify',
|
|
2697
|
+
sessionId: request.sessionId,
|
|
2698
|
+
assistantMessage: message,
|
|
2699
|
+
statusText: 'Handoff governado necessario.',
|
|
2700
|
+
canApply: false,
|
|
2701
|
+
quickReplies: [
|
|
2702
|
+
{
|
|
2703
|
+
id: 'shared-rule-handoff',
|
|
2704
|
+
label: 'Continuar como regra governada',
|
|
2705
|
+
prompt,
|
|
2706
|
+
kind: 'shared-rule-handoff',
|
|
2707
|
+
description: 'Criar intake de domain-rules em vez de aplicar patch local nas abas.',
|
|
2708
|
+
icon: 'rule',
|
|
2709
|
+
tone: 'warning',
|
|
2710
|
+
contextHints: {
|
|
2711
|
+
flowId: 'shared_rule_authoring',
|
|
2712
|
+
source: 'praxis-tabs',
|
|
2713
|
+
recommendedAction: 'domain-rules/intake',
|
|
2714
|
+
},
|
|
2715
|
+
},
|
|
2716
|
+
],
|
|
2717
|
+
clarificationQuestions: [
|
|
2718
|
+
{
|
|
2719
|
+
id: 'tabs-governed-rule-confirmation',
|
|
2720
|
+
type: 'confirm',
|
|
2721
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
2722
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
2723
|
+
required: true,
|
|
2724
|
+
options: [
|
|
2725
|
+
{
|
|
2726
|
+
id: 'shared-rule-handoff',
|
|
2727
|
+
label: 'Sim, continuar governado',
|
|
2728
|
+
value: prompt,
|
|
2729
|
+
description: 'Nao aplicar como patch local das abas.',
|
|
2730
|
+
contextHints: {
|
|
2731
|
+
flowId: 'shared_rule_authoring',
|
|
2732
|
+
source: 'praxis-tabs',
|
|
2733
|
+
},
|
|
2734
|
+
},
|
|
2735
|
+
],
|
|
2736
|
+
},
|
|
2737
|
+
],
|
|
2738
|
+
diagnostics: {
|
|
2739
|
+
governedDecisionHandoff: {
|
|
2740
|
+
flowId: 'shared_rule_authoring',
|
|
2741
|
+
sourcePrompt: prompt,
|
|
2742
|
+
sourceComponent: 'praxis-tabs',
|
|
2743
|
+
},
|
|
2744
|
+
},
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
optionalJsonObject(value) {
|
|
2748
|
+
if (value === undefined || value === null) {
|
|
2749
|
+
return undefined;
|
|
2750
|
+
}
|
|
2751
|
+
const object = this.toAiJsonObject(value);
|
|
2752
|
+
return Object.keys(object).length ? object : undefined;
|
|
2753
|
+
}
|
|
2754
|
+
toAiJsonObject(value) {
|
|
2755
|
+
const record = this.toRecord(value);
|
|
2756
|
+
if (!record) {
|
|
2757
|
+
return {};
|
|
2758
|
+
}
|
|
2759
|
+
try {
|
|
2760
|
+
return JSON.parse(JSON.stringify(record));
|
|
2761
|
+
}
|
|
2762
|
+
catch {
|
|
2763
|
+
return {};
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
toRecord(value) {
|
|
2767
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
2768
|
+
? value
|
|
2769
|
+
: null;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2358
2773
|
class PraxisTabs {
|
|
2359
2774
|
i18n = inject(PraxisI18nService);
|
|
2360
2775
|
settings = inject(SettingsPanelService);
|
|
@@ -2362,16 +2777,34 @@ class PraxisTabs {
|
|
|
2362
2777
|
snack = inject(MatSnackBar);
|
|
2363
2778
|
componentKeys = inject(ComponentKeyService);
|
|
2364
2779
|
logger = inject(LoggerService);
|
|
2780
|
+
cdr = inject(ChangeDetectorRef);
|
|
2781
|
+
aiApi = inject(AiBackendApiService);
|
|
2782
|
+
assistantSessions = inject(PraxisAssistantSessionRegistryService);
|
|
2783
|
+
aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
|
|
2365
2784
|
route = (() => { try {
|
|
2366
2785
|
return inject(ActivatedRoute);
|
|
2367
2786
|
}
|
|
2368
2787
|
catch {
|
|
2369
2788
|
return undefined;
|
|
2370
2789
|
} })();
|
|
2790
|
+
aiAssistantSessionEffect = effect(() => {
|
|
2791
|
+
const session = this.assistantSessions.activeSession();
|
|
2792
|
+
if (!session || session.id !== this.resolveAiAssistantSessionId())
|
|
2793
|
+
return;
|
|
2794
|
+
if (!this.aiAssistantOpen) {
|
|
2795
|
+
this.openAiAssistantFromSession(session);
|
|
2796
|
+
}
|
|
2797
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
|
|
2371
2798
|
warnedMissingId = false;
|
|
2372
2799
|
config = null;
|
|
2373
2800
|
tabsId;
|
|
2374
2801
|
componentInstanceId;
|
|
2802
|
+
set selectedIndex(index) {
|
|
2803
|
+
if (index == null)
|
|
2804
|
+
return;
|
|
2805
|
+
this.controlledSelectedIndex = index;
|
|
2806
|
+
this.applySelectedIndex(index, false, false);
|
|
2807
|
+
}
|
|
2375
2808
|
enableCustomization = false;
|
|
2376
2809
|
form = null;
|
|
2377
2810
|
context = null;
|
|
@@ -2383,11 +2816,27 @@ class PraxisTabs {
|
|
|
2383
2816
|
selectFocusedIndex = new EventEmitter();
|
|
2384
2817
|
widgetEvent = new EventEmitter();
|
|
2385
2818
|
aiAdapter = new TabsAiAdapter(this);
|
|
2819
|
+
aiAssistantOpen = false;
|
|
2820
|
+
aiAssistantPrompt = '';
|
|
2821
|
+
aiAssistantViewState = null;
|
|
2822
|
+
aiAssistantLayout = createPraxisAssistantViewportLayout();
|
|
2823
|
+
aiAssistantLabels = {
|
|
2824
|
+
title: 'Copiloto semantico Praxis',
|
|
2825
|
+
subtitle: 'Converse, revise e governe ajustes das abas.',
|
|
2826
|
+
prompt: 'Mensagem',
|
|
2827
|
+
promptPlaceholder: 'Descreva o ajuste que voce precisa nas abas.',
|
|
2828
|
+
emptyConversation: 'Diga o que voce quer alterar nas abas.',
|
|
2829
|
+
submit: 'Interpretar pedido',
|
|
2830
|
+
apply: 'Aplicar ajuste',
|
|
2831
|
+
};
|
|
2832
|
+
aiAssistantController = null;
|
|
2833
|
+
aiAssistantStateSubscription = null;
|
|
2386
2834
|
// Signals to manage local state for selection in Nav mode and Group mode
|
|
2387
2835
|
currentNavIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentNavIndex" }] : []));
|
|
2388
2836
|
selectedIndexSignal = signal(0, ...(ngDevMode ? [{ debugName: "selectedIndexSignal" }] : []));
|
|
2389
2837
|
groupLoaded = new Set();
|
|
2390
2838
|
navLoaded = new Set();
|
|
2839
|
+
controlledSelectedIndex;
|
|
2391
2840
|
destroy$ = new Subject();
|
|
2392
2841
|
widgetDefinitionCache = new WeakMap();
|
|
2393
2842
|
ngOnInit() {
|
|
@@ -2400,6 +2849,7 @@ class PraxisTabs {
|
|
|
2400
2849
|
this.config = stored;
|
|
2401
2850
|
}
|
|
2402
2851
|
this.syncSelectionFromConfig();
|
|
2852
|
+
this.reapplyControlledSelectedIndex();
|
|
2403
2853
|
});
|
|
2404
2854
|
}
|
|
2405
2855
|
}
|
|
@@ -2409,9 +2859,12 @@ class PraxisTabs {
|
|
|
2409
2859
|
this.syncSelectionFromConfig();
|
|
2410
2860
|
// Persist when tabsId provided
|
|
2411
2861
|
this.persistConfig(this.config);
|
|
2862
|
+
this.reapplyControlledSelectedIndex();
|
|
2412
2863
|
}
|
|
2413
2864
|
}
|
|
2414
2865
|
ngOnDestroy() {
|
|
2866
|
+
this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
|
|
2867
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
2415
2868
|
this.destroy$.next();
|
|
2416
2869
|
this.destroy$.complete();
|
|
2417
2870
|
}
|
|
@@ -2427,23 +2880,39 @@ class PraxisTabs {
|
|
|
2427
2880
|
getNavActive(i) {
|
|
2428
2881
|
return this.currentNavIndex() === i;
|
|
2429
2882
|
}
|
|
2883
|
+
visibleNavLinkEntries() {
|
|
2884
|
+
return (this.config?.nav?.links ?? [])
|
|
2885
|
+
.map((link, index) => ({ link, index }))
|
|
2886
|
+
.filter((entry) => entry.link.visible !== false);
|
|
2887
|
+
}
|
|
2888
|
+
visibleTabEntries() {
|
|
2889
|
+
return (this.config?.tabs ?? [])
|
|
2890
|
+
.map((tab, index) => ({ tab, index }))
|
|
2891
|
+
.filter((entry) => entry.tab.visible !== false);
|
|
2892
|
+
}
|
|
2893
|
+
selectedVisibleNavIndex() {
|
|
2894
|
+
const entries = this.visibleNavLinkEntries();
|
|
2895
|
+
const index = entries.findIndex((entry) => entry.index === this.currentNavIndex());
|
|
2896
|
+
return index >= 0 ? index : 0;
|
|
2897
|
+
}
|
|
2898
|
+
selectedVisibleTabIndex() {
|
|
2899
|
+
const entries = this.visibleTabEntries();
|
|
2900
|
+
const index = entries.findIndex((entry) => entry.index === this.selectedIndexSignal());
|
|
2901
|
+
return index >= 0 ? index : 0;
|
|
2902
|
+
}
|
|
2903
|
+
onVisibleTabIndexChange(index) {
|
|
2904
|
+
const entry = this.visibleTabEntries()[index];
|
|
2905
|
+
if (!entry)
|
|
2906
|
+
return;
|
|
2907
|
+
this.onSelectedIndexChange(entry.index);
|
|
2908
|
+
}
|
|
2430
2909
|
onNavClick(i) {
|
|
2431
2910
|
if (!this.config?.nav?.links?.length)
|
|
2432
2911
|
return;
|
|
2433
2912
|
const linksCount = this.config.nav.links.length;
|
|
2434
2913
|
if (i < 0 || i >= linksCount)
|
|
2435
2914
|
return;
|
|
2436
|
-
this.
|
|
2437
|
-
this.config = produce(this.config, (draft) => {
|
|
2438
|
-
if (!draft.nav)
|
|
2439
|
-
return;
|
|
2440
|
-
draft.nav.selectedIndex = i;
|
|
2441
|
-
});
|
|
2442
|
-
this.persistConfig(this.config);
|
|
2443
|
-
// Lazy: mark as loaded
|
|
2444
|
-
this.navLoaded.add(i);
|
|
2445
|
-
// Emit as index change for consumers to track
|
|
2446
|
-
this.selectedIndexChange.emit(i);
|
|
2915
|
+
this.applySelectedIndex(i, true);
|
|
2447
2916
|
}
|
|
2448
2917
|
onNavDrop(event) {
|
|
2449
2918
|
if (!this.config?.nav?.links)
|
|
@@ -2491,10 +2960,39 @@ class PraxisTabs {
|
|
|
2491
2960
|
});
|
|
2492
2961
|
this.persistConfig(this.config);
|
|
2493
2962
|
}
|
|
2963
|
+
onVisibleNavDrop(event) {
|
|
2964
|
+
const entries = this.visibleNavLinkEntries();
|
|
2965
|
+
const previous = entries[event.previousIndex];
|
|
2966
|
+
const current = entries[event.currentIndex];
|
|
2967
|
+
if (!previous || !current)
|
|
2968
|
+
return;
|
|
2969
|
+
this.onNavDrop({
|
|
2970
|
+
...event,
|
|
2971
|
+
previousIndex: previous.index,
|
|
2972
|
+
currentIndex: current.index,
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2494
2975
|
onSelectedIndexChange(index) {
|
|
2976
|
+
this.applySelectedIndex(index, true);
|
|
2977
|
+
}
|
|
2978
|
+
applySelectedIndex(index, emit, persist = true) {
|
|
2979
|
+
if (this.isNavMode() && this.config) {
|
|
2980
|
+
const selected = this.clampIndex(index, this.config?.nav?.links?.length ?? 0);
|
|
2981
|
+
this.currentNavIndex.set(selected);
|
|
2982
|
+
this.config = produce(this.config, (draft) => {
|
|
2983
|
+
draft.nav.selectedIndex = selected;
|
|
2984
|
+
});
|
|
2985
|
+
if (persist) {
|
|
2986
|
+
this.persistConfig(this.config);
|
|
2987
|
+
}
|
|
2988
|
+
this.navLoaded.add(selected);
|
|
2989
|
+
if (emit) {
|
|
2990
|
+
this.selectedIndexChange.emit(selected);
|
|
2991
|
+
}
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2495
2994
|
const selected = this.clampIndex(index, this.config?.tabs?.length ?? 0);
|
|
2496
2995
|
this.selectedIndexSignal.set(selected);
|
|
2497
|
-
// Update config immutably
|
|
2498
2996
|
if (this.config) {
|
|
2499
2997
|
this.config = produce(this.config, (draft) => {
|
|
2500
2998
|
if (!draft.group) {
|
|
@@ -2504,11 +3002,20 @@ class PraxisTabs {
|
|
|
2504
3002
|
draft.group.selectedIndex = selected;
|
|
2505
3003
|
}
|
|
2506
3004
|
});
|
|
2507
|
-
|
|
3005
|
+
if (persist) {
|
|
3006
|
+
this.persistConfig(this.config);
|
|
3007
|
+
}
|
|
2508
3008
|
}
|
|
2509
|
-
// Lazy: mark as loaded
|
|
2510
3009
|
this.groupLoaded.add(selected);
|
|
2511
|
-
|
|
3010
|
+
if (emit) {
|
|
3011
|
+
this.selectedIndexChange.emit(selected);
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
reapplyControlledSelectedIndex() {
|
|
3015
|
+
if (this.controlledSelectedIndex == null) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
this.applySelectedIndex(this.controlledSelectedIndex, false, false);
|
|
2512
3019
|
}
|
|
2513
3020
|
closeTab(index) {
|
|
2514
3021
|
if (!this.config?.tabs)
|
|
@@ -2596,6 +3103,305 @@ class PraxisTabs {
|
|
|
2596
3103
|
ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
|
|
2597
3104
|
ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
|
|
2598
3105
|
}
|
|
3106
|
+
openAiAssistant() {
|
|
3107
|
+
this.initializeAiAssistantController();
|
|
3108
|
+
this.aiAssistantOpen = true;
|
|
3109
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
3110
|
+
this.syncAiAssistantSession('active');
|
|
3111
|
+
this.cdr.markForCheck();
|
|
3112
|
+
}
|
|
3113
|
+
openAiAssistantFromSession(session) {
|
|
3114
|
+
if (session.id !== this.resolveAiAssistantSessionId())
|
|
3115
|
+
return;
|
|
3116
|
+
this.initializeAiAssistantController();
|
|
3117
|
+
this.aiAssistantOpen = true;
|
|
3118
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
3119
|
+
this.syncAiAssistantSession('active');
|
|
3120
|
+
this.cdr.markForCheck();
|
|
3121
|
+
}
|
|
3122
|
+
closeAiAssistant() {
|
|
3123
|
+
this.aiAssistantOpen = false;
|
|
3124
|
+
this.syncAiAssistantSession('minimized');
|
|
3125
|
+
this.cdr.markForCheck();
|
|
3126
|
+
}
|
|
3127
|
+
onAiAssistantPromptChange(prompt) {
|
|
3128
|
+
this.aiAssistantPrompt = prompt;
|
|
3129
|
+
this.syncAiAssistantSession();
|
|
3130
|
+
}
|
|
3131
|
+
onAiAssistantSubmit(prompt) {
|
|
3132
|
+
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
3133
|
+
this.aiAssistantPrompt = '';
|
|
3134
|
+
this.aiAssistantViewState = state;
|
|
3135
|
+
this.syncAiAssistantSession();
|
|
3136
|
+
this.cdr.markForCheck();
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
onAiAssistantApply() {
|
|
3140
|
+
this.aiAssistantController?.apply().subscribe((state) => {
|
|
3141
|
+
this.aiAssistantViewState = state;
|
|
3142
|
+
this.syncAiAssistantSession();
|
|
3143
|
+
this.cdr.markForCheck();
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
onAiAssistantRetry() {
|
|
3147
|
+
this.aiAssistantController?.retry().subscribe((state) => {
|
|
3148
|
+
this.aiAssistantViewState = state;
|
|
3149
|
+
this.syncAiAssistantSession();
|
|
3150
|
+
this.cdr.markForCheck();
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
onAiAssistantCancel() {
|
|
3154
|
+
this.aiAssistantController?.cancel().subscribe((state) => {
|
|
3155
|
+
this.aiAssistantPrompt = '';
|
|
3156
|
+
this.aiAssistantViewState = state;
|
|
3157
|
+
this.syncAiAssistantSession();
|
|
3158
|
+
this.cdr.markForCheck();
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
onAiAssistantQuickReply(reply) {
|
|
3162
|
+
const controller = this.aiAssistantController;
|
|
3163
|
+
if (!controller)
|
|
3164
|
+
return;
|
|
3165
|
+
const state = controller.snapshot();
|
|
3166
|
+
const next$ = state.state === 'clarification'
|
|
3167
|
+
? controller.answerClarification(reply.prompt)
|
|
3168
|
+
: controller.submitPrompt(reply.prompt, {
|
|
3169
|
+
kind: reply.kind || 'quick-reply',
|
|
3170
|
+
id: reply.id,
|
|
3171
|
+
value: reply.prompt,
|
|
3172
|
+
});
|
|
3173
|
+
next$.subscribe((nextState) => {
|
|
3174
|
+
this.aiAssistantPrompt = '';
|
|
3175
|
+
this.aiAssistantViewState = nextState;
|
|
3176
|
+
this.syncAiAssistantSession();
|
|
3177
|
+
this.cdr.markForCheck();
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
onAiAssistantEditMessage(message) {
|
|
3181
|
+
this.aiAssistantPrompt = message.text;
|
|
3182
|
+
this.cdr.markForCheck();
|
|
3183
|
+
}
|
|
3184
|
+
onAiAssistantResendMessage(message) {
|
|
3185
|
+
this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
|
|
3186
|
+
this.aiAssistantPrompt = '';
|
|
3187
|
+
this.aiAssistantViewState = state;
|
|
3188
|
+
this.syncAiAssistantSession();
|
|
3189
|
+
this.cdr.markForCheck();
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
onAiAssistantLayoutChange(layout) {
|
|
3193
|
+
this.aiAssistantLayout = layout;
|
|
3194
|
+
}
|
|
3195
|
+
initializeAiAssistantController() {
|
|
3196
|
+
if (this.aiAssistantController)
|
|
3197
|
+
return;
|
|
3198
|
+
const flow = new TabsAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
|
|
3199
|
+
const controller = this.aiTurnOrchestrator.createController(flow, {
|
|
3200
|
+
componentId: this.aiAdapter.componentId || 'praxis-tabs',
|
|
3201
|
+
componentType: this.aiAdapter.componentType || 'tabs',
|
|
3202
|
+
contextItems: this.buildAiAssistantContextItems(),
|
|
3203
|
+
});
|
|
3204
|
+
this.aiAssistantController = controller;
|
|
3205
|
+
this.aiAssistantViewState = controller.snapshot();
|
|
3206
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
3207
|
+
this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
|
|
3208
|
+
this.aiAssistantViewState = state;
|
|
3209
|
+
this.syncAiAssistantSession();
|
|
3210
|
+
this.cdr.markForCheck();
|
|
3211
|
+
});
|
|
3212
|
+
this.cdr.markForCheck();
|
|
3213
|
+
}
|
|
3214
|
+
buildAiAssistantContextItems() {
|
|
3215
|
+
const items = [
|
|
3216
|
+
{
|
|
3217
|
+
id: 'component',
|
|
3218
|
+
label: 'Componente',
|
|
3219
|
+
value: 'Abas',
|
|
3220
|
+
kind: 'component',
|
|
3221
|
+
icon: 'tab',
|
|
3222
|
+
},
|
|
3223
|
+
{
|
|
3224
|
+
id: 'mode',
|
|
3225
|
+
label: 'Modo',
|
|
3226
|
+
value: this.isNavMode() ? 'nav' : 'group',
|
|
3227
|
+
kind: 'custom',
|
|
3228
|
+
icon: 'account_tree',
|
|
3229
|
+
},
|
|
3230
|
+
];
|
|
3231
|
+
if (this.tabsId) {
|
|
3232
|
+
items.push({
|
|
3233
|
+
id: 'tabs-id',
|
|
3234
|
+
label: 'Tabs',
|
|
3235
|
+
value: this.tabsId,
|
|
3236
|
+
kind: 'custom',
|
|
3237
|
+
icon: 'tag',
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
return items;
|
|
3241
|
+
}
|
|
3242
|
+
buildAiAssistantContextSnapshot() {
|
|
3243
|
+
const fieldNames = this.collectContentFieldNames();
|
|
3244
|
+
const counts = this.collectTabsCounts();
|
|
3245
|
+
return {
|
|
3246
|
+
identity: {
|
|
3247
|
+
sessionId: this.resolveAiAssistantSessionId(),
|
|
3248
|
+
ownerId: this.resolveAiAssistantOwnerId(),
|
|
3249
|
+
ownerType: 'tabs',
|
|
3250
|
+
componentId: 'praxis-tabs',
|
|
3251
|
+
componentType: 'tabs',
|
|
3252
|
+
routeKey: this.resolveAiAssistantRouteKey(),
|
|
3253
|
+
},
|
|
3254
|
+
target: {
|
|
3255
|
+
kind: 'component',
|
|
3256
|
+
id: this.resolveAiAssistantOwnerId(),
|
|
3257
|
+
label: this.tabsId || 'Abas',
|
|
3258
|
+
metadata: {
|
|
3259
|
+
mode: this.isNavMode() ? 'nav' : 'group',
|
|
3260
|
+
hasCustomization: !!this.enableCustomization,
|
|
3261
|
+
},
|
|
3262
|
+
},
|
|
3263
|
+
contextItems: this.buildAiAssistantContextItems().map((item) => ({
|
|
3264
|
+
id: item.id,
|
|
3265
|
+
label: item.label,
|
|
3266
|
+
value: item.value || '',
|
|
3267
|
+
kind: item.kind,
|
|
3268
|
+
})),
|
|
3269
|
+
mode: 'agentic-authoring',
|
|
3270
|
+
authoringManifestRef: {
|
|
3271
|
+
componentId: 'praxis-tabs',
|
|
3272
|
+
source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
|
|
3273
|
+
},
|
|
3274
|
+
schemaFields: fieldNames.length ? fieldNames : undefined,
|
|
3275
|
+
dataProfileDigest: {
|
|
3276
|
+
summary: `${counts.tabCount} aba(s), ${counts.linkCount} link(s), ${counts.widgetCount} widget(s), ${counts.fieldCount} campo(s)`,
|
|
3277
|
+
counts,
|
|
3278
|
+
},
|
|
3279
|
+
runtimeStateDigest: {
|
|
3280
|
+
summary: this.isNavMode()
|
|
3281
|
+
? `Nav ativo no indice ${this.currentNavIndex()}`
|
|
3282
|
+
: `Grupo ativo no indice ${this.selectedIndexSignal()}`,
|
|
3283
|
+
fields: [
|
|
3284
|
+
this.isNavMode() ? 'nav.links' : 'tabs',
|
|
3285
|
+
'selectedIndex',
|
|
3286
|
+
'widgetEvent',
|
|
3287
|
+
],
|
|
3288
|
+
},
|
|
3289
|
+
capabilityRefs: [
|
|
3290
|
+
{
|
|
3291
|
+
id: 'tabs.component-edit-plan',
|
|
3292
|
+
label: 'Plano de edicao de abas',
|
|
3293
|
+
source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
|
|
3294
|
+
risk: 'medium',
|
|
3295
|
+
},
|
|
3296
|
+
],
|
|
3297
|
+
governanceHints: [
|
|
3298
|
+
{
|
|
3299
|
+
kind: 'business-rule-boundary',
|
|
3300
|
+
label: 'Regras compartilhadas exigem governanca',
|
|
3301
|
+
reason: 'Politicas, validacoes reutilizaveis e compliance nao devem ser aplicados como patch local das abas.',
|
|
3302
|
+
risk: 'high',
|
|
3303
|
+
},
|
|
3304
|
+
],
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
syncAiAssistantSession(visibility = null) {
|
|
3308
|
+
if (!this.enableCustomization)
|
|
3309
|
+
return;
|
|
3310
|
+
if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState())
|
|
3311
|
+
return;
|
|
3312
|
+
const state = this.aiAssistantViewState;
|
|
3313
|
+
this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
|
|
3314
|
+
title: 'Copiloto semantico Praxis',
|
|
3315
|
+
summary: this.resolveAiAssistantSummary(),
|
|
3316
|
+
mode: state?.mode || 'agentic-authoring',
|
|
3317
|
+
state: state?.state || 'idle',
|
|
3318
|
+
visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
|
|
3319
|
+
badge: this.resolveAiAssistantBadge(),
|
|
3320
|
+
icon: this.resolveAiAssistantIcon(),
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
hasAiAssistantSessionState() {
|
|
3324
|
+
return !!this.aiAssistantPrompt.trim()
|
|
3325
|
+
|| !!this.aiAssistantViewState?.messages?.length
|
|
3326
|
+
|| !!this.aiAssistantViewState?.quickReplies?.length
|
|
3327
|
+
|| !!this.aiAssistantViewState?.pendingPatch
|
|
3328
|
+
|| !!this.aiAssistantViewState?.statusText?.trim()
|
|
3329
|
+
|| !!this.aiAssistantViewState?.errorText?.trim();
|
|
3330
|
+
}
|
|
3331
|
+
resolveAiAssistantSessionId() {
|
|
3332
|
+
return `tabs:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
|
|
3333
|
+
}
|
|
3334
|
+
resolveAiAssistantOwnerId() {
|
|
3335
|
+
return (this.componentInstanceId || this.tabsId || 'tabs').trim() || 'tabs';
|
|
3336
|
+
}
|
|
3337
|
+
resolveAiAssistantRouteKey() {
|
|
3338
|
+
const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
|
|
3339
|
+
return routePath || 'local';
|
|
3340
|
+
}
|
|
3341
|
+
resolveAiAssistantSummary() {
|
|
3342
|
+
const status = this.aiAssistantViewState?.statusText?.trim();
|
|
3343
|
+
if (status)
|
|
3344
|
+
return status;
|
|
3345
|
+
const error = this.aiAssistantViewState?.errorText?.trim();
|
|
3346
|
+
if (error)
|
|
3347
|
+
return error;
|
|
3348
|
+
const prompt = this.aiAssistantPrompt.trim();
|
|
3349
|
+
if (prompt)
|
|
3350
|
+
return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
|
|
3351
|
+
const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
|
|
3352
|
+
.find((message) => message.role === 'assistant' || message.role === 'user');
|
|
3353
|
+
if (lastMessage?.text) {
|
|
3354
|
+
return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
|
|
3355
|
+
}
|
|
3356
|
+
return this.isNavMode() ? 'Assistente contextual das abas de navegacao.' : 'Assistente contextual do grupo de abas.';
|
|
3357
|
+
}
|
|
3358
|
+
resolveAiAssistantBadge() {
|
|
3359
|
+
const state = this.aiAssistantViewState?.state;
|
|
3360
|
+
if (state === 'error')
|
|
3361
|
+
return 'erro';
|
|
3362
|
+
if (state === 'clarification')
|
|
3363
|
+
return 'revisar';
|
|
3364
|
+
if (state === 'review')
|
|
3365
|
+
return 'preview';
|
|
3366
|
+
if (state === 'success')
|
|
3367
|
+
return 'ok';
|
|
3368
|
+
return undefined;
|
|
3369
|
+
}
|
|
3370
|
+
resolveAiAssistantIcon() {
|
|
3371
|
+
const state = this.aiAssistantViewState?.state;
|
|
3372
|
+
if (state === 'error')
|
|
3373
|
+
return 'error';
|
|
3374
|
+
if (state === 'clarification')
|
|
3375
|
+
return 'rule';
|
|
3376
|
+
if (state === 'review')
|
|
3377
|
+
return 'rate_review';
|
|
3378
|
+
return 'auto_awesome';
|
|
3379
|
+
}
|
|
3380
|
+
collectContentFieldNames() {
|
|
3381
|
+
const fields = [
|
|
3382
|
+
...(this.config?.tabs ?? []).flatMap((tab) => tab.content ?? []),
|
|
3383
|
+
...(this.config?.nav?.links ?? []).flatMap((link) => link.content ?? []),
|
|
3384
|
+
];
|
|
3385
|
+
return Array.from(new Set(fields
|
|
3386
|
+
.map((field) => field?.name || field?.key || field?.id)
|
|
3387
|
+
.filter((name) => typeof name === 'string' && !!name.trim())));
|
|
3388
|
+
}
|
|
3389
|
+
collectTabsCounts() {
|
|
3390
|
+
const tabs = this.config?.tabs ?? [];
|
|
3391
|
+
const links = this.config?.nav?.links ?? [];
|
|
3392
|
+
return {
|
|
3393
|
+
tabCount: tabs.length,
|
|
3394
|
+
linkCount: links.length,
|
|
3395
|
+
widgetCount: [
|
|
3396
|
+
...tabs.flatMap((tab) => tab.widgets ?? []),
|
|
3397
|
+
...links.flatMap((link) => link.widgets ?? []),
|
|
3398
|
+
].length,
|
|
3399
|
+
fieldCount: [
|
|
3400
|
+
...tabs.flatMap((tab) => tab.content ?? []),
|
|
3401
|
+
...links.flatMap((link) => link.content ?? []),
|
|
3402
|
+
].length,
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
2599
3405
|
addEmptyTab() {
|
|
2600
3406
|
const next = produce(this.config || {}, (draft) => {
|
|
2601
3407
|
if (!draft.group)
|
|
@@ -2691,19 +3497,26 @@ class PraxisTabs {
|
|
|
2691
3497
|
return id ? `tabs:${id}` : null;
|
|
2692
3498
|
}
|
|
2693
3499
|
syncSelectionFromConfig() {
|
|
2694
|
-
this.groupLoaded.clear();
|
|
2695
|
-
this.navLoaded.clear();
|
|
2696
3500
|
const tabsLength = this.config?.tabs?.length ?? 0;
|
|
2697
3501
|
const linksLength = this.config?.nav?.links?.length ?? 0;
|
|
2698
3502
|
const groupIndex = this.clampIndex(this.config?.group?.selectedIndex, tabsLength);
|
|
2699
3503
|
const navIndex = this.clampIndex(this.config?.nav?.selectedIndex, linksLength);
|
|
2700
3504
|
this.selectedIndexSignal.set(groupIndex);
|
|
2701
3505
|
this.currentNavIndex.set(navIndex);
|
|
3506
|
+
this.pruneLoadedIndexes(this.groupLoaded, tabsLength);
|
|
3507
|
+
this.pruneLoadedIndexes(this.navLoaded, linksLength);
|
|
2702
3508
|
if (tabsLength > 0)
|
|
2703
3509
|
this.groupLoaded.add(groupIndex);
|
|
2704
3510
|
if (linksLength > 0)
|
|
2705
3511
|
this.navLoaded.add(navIndex);
|
|
2706
3512
|
}
|
|
3513
|
+
pruneLoadedIndexes(indexes, size) {
|
|
3514
|
+
for (const index of Array.from(indexes)) {
|
|
3515
|
+
if (index < 0 || index >= size) {
|
|
3516
|
+
indexes.delete(index);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
2707
3520
|
persistConfig(config) {
|
|
2708
3521
|
const key = this.storageKey();
|
|
2709
3522
|
if (!key || !config)
|
|
@@ -2764,10 +3577,16 @@ class PraxisTabs {
|
|
|
2764
3577
|
return !this.isLazy() || this.navLoaded.has(index) || this.currentNavIndex() === index;
|
|
2765
3578
|
}
|
|
2766
3579
|
isEmptyGlobal() {
|
|
2767
|
-
const hasTabs =
|
|
2768
|
-
const hasLinks =
|
|
3580
|
+
const hasTabs = this.visibleTabEntries().length > 0;
|
|
3581
|
+
const hasLinks = this.visibleNavLinkEntries().length > 0;
|
|
2769
3582
|
return !(hasTabs || hasLinks);
|
|
2770
3583
|
}
|
|
3584
|
+
trackVisibleNavLink(index, entry) {
|
|
3585
|
+
return entry.link.id || `${entry.link.label || 'nav-link'}:${entry.index ?? index}`;
|
|
3586
|
+
}
|
|
3587
|
+
trackVisibleTab(index, entry) {
|
|
3588
|
+
return entry.tab.id || entry.tab.textLabel || `tab:${entry.index ?? index}`;
|
|
3589
|
+
}
|
|
2771
3590
|
trackNavLink(index, link) {
|
|
2772
3591
|
return link.id || `${link.label || 'nav-link'}:${index}`;
|
|
2773
3592
|
}
|
|
@@ -2952,7 +3771,7 @@ class PraxisTabs {
|
|
|
2952
3771
|
return JSON.parse(JSON.stringify(widget));
|
|
2953
3772
|
}
|
|
2954
3773
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2955
|
-
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", 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: `
|
|
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: `
|
|
2956
3775
|
<div
|
|
2957
3776
|
class="praxis-tabs-root"
|
|
2958
3777
|
[class.density-compact]="config?.appearance?.density === 'compact'"
|
|
@@ -2969,9 +3788,51 @@ class PraxisTabs {
|
|
|
2969
3788
|
<style *ngIf="styleCss() as s" [innerHTML]="s"></style>
|
|
2970
3789
|
|
|
2971
3790
|
<div class="tabs-ai-assistant" *ngIf="enableCustomization">
|
|
2972
|
-
<
|
|
3791
|
+
<button
|
|
3792
|
+
mat-mini-fab
|
|
3793
|
+
type="button"
|
|
3794
|
+
color="primary"
|
|
3795
|
+
class="tabs-ai-assistant-trigger"
|
|
3796
|
+
(click)="openAiAssistant()"
|
|
3797
|
+
matTooltip="Copiloto semantico Praxis"
|
|
3798
|
+
aria-label="Abrir copiloto semantico Praxis das abas"
|
|
3799
|
+
data-testid="praxis-tabs-ai-assistant-trigger"
|
|
3800
|
+
>
|
|
3801
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
3802
|
+
</button>
|
|
2973
3803
|
</div>
|
|
2974
3804
|
|
|
3805
|
+
<praxis-ai-assistant-shell
|
|
3806
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
3807
|
+
[labels]="aiAssistantLabels"
|
|
3808
|
+
[mode]="aiAssistantViewState.mode"
|
|
3809
|
+
[state]="aiAssistantViewState.state"
|
|
3810
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
3811
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
3812
|
+
[messages]="aiAssistantViewState.messages"
|
|
3813
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
3814
|
+
[prompt]="aiAssistantPrompt"
|
|
3815
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
3816
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
3817
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
3818
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
3819
|
+
[layout]="aiAssistantLayout"
|
|
3820
|
+
testIdPrefix="praxis-tabs-ai-assistant"
|
|
3821
|
+
panelTestId="praxis-tabs-ai-assistant-panel"
|
|
3822
|
+
submitTestId="praxis-tabs-ai-assistant-submit"
|
|
3823
|
+
applyTestId="praxis-tabs-ai-assistant-apply"
|
|
3824
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
3825
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
3826
|
+
(apply)="onAiAssistantApply()"
|
|
3827
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
3828
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
3829
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
3830
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
3831
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
3832
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
3833
|
+
(close)="closeAiAssistant()"
|
|
3834
|
+
></praxis-ai-assistant-shell>
|
|
3835
|
+
|
|
2975
3836
|
<!-- Empty state (global) -->
|
|
2976
3837
|
<ng-container *ngIf="isEmptyGlobal(); else notEmpty">
|
|
2977
3838
|
<praxis-empty-state-card
|
|
@@ -2995,13 +3856,13 @@ class PraxisTabs {
|
|
|
2995
3856
|
cdkDropList
|
|
2996
3857
|
cdkDropListOrientation="horizontal"
|
|
2997
3858
|
[cdkDropListDisabled]="!config?.behavior?.reorderable"
|
|
2998
|
-
(cdkDropListDropped)="
|
|
3859
|
+
(cdkDropListDropped)="onVisibleNavDrop($event)"
|
|
2999
3860
|
[disablePagination]="config?.nav?.disablePagination"
|
|
3000
3861
|
[fitInkBarToContent]="config?.nav?.fitInkBarToContent"
|
|
3001
3862
|
[mat-stretch-tabs]="config?.nav?.stretchTabs"
|
|
3002
3863
|
[color]="config?.nav?.color"
|
|
3003
3864
|
[backgroundColor]="config?.nav?.backgroundColor"
|
|
3004
|
-
[selectedIndex]="
|
|
3865
|
+
[selectedIndex]="selectedVisibleNavIndex()"
|
|
3005
3866
|
[attr.aria-label]="config?.nav?.ariaLabel || config?.group?.ariaLabel || null"
|
|
3006
3867
|
[attr.aria-labelledby]="config?.nav?.ariaLabelledby || config?.group?.ariaLabelledby || null"
|
|
3007
3868
|
[animationDuration]="effectiveAnimationDuration()"
|
|
@@ -3010,21 +3871,22 @@ class PraxisTabs {
|
|
|
3010
3871
|
>
|
|
3011
3872
|
<a
|
|
3012
3873
|
mat-tab-link
|
|
3013
|
-
*ngFor="let
|
|
3874
|
+
*ngFor="let entry of visibleNavLinkEntries(); let i = index; trackBy: trackVisibleNavLink"
|
|
3014
3875
|
cdkDrag
|
|
3015
3876
|
[cdkDragDisabled]="!config?.behavior?.reorderable"
|
|
3016
3877
|
cdkDragLockAxis="x"
|
|
3017
|
-
[active]="getNavActive(
|
|
3018
|
-
[disabled]="link.disabled"
|
|
3019
|
-
[disableRipple]="config?.nav?.disableRipple || link.disableRipple"
|
|
3020
|
-
[fitInkBarToContent]="link.fitInkBarToContent || false"
|
|
3021
|
-
[id]="link.id || ''"
|
|
3022
|
-
(click)="onNavClick(
|
|
3878
|
+
[active]="getNavActive(entry.index)"
|
|
3879
|
+
[disabled]="entry.link.disabled"
|
|
3880
|
+
[disableRipple]="config?.nav?.disableRipple || entry.link.disableRipple"
|
|
3881
|
+
[fitInkBarToContent]="entry.link.fitInkBarToContent || false"
|
|
3882
|
+
[id]="entry.link.id || ''"
|
|
3883
|
+
(click)="onNavClick(entry.index)"
|
|
3023
3884
|
>
|
|
3024
3885
|
<span *ngIf="config?.behavior?.reorderable" class="drag-handle" cdkDragHandle>
|
|
3025
3886
|
<mat-icon fontIcon="drag_indicator"></mat-icon>
|
|
3026
3887
|
</span>
|
|
3027
|
-
|
|
3888
|
+
<mat-icon *ngIf="entry.link.icon" class="tab-label-icon" [praxisIcon]="entry.link.icon"></mat-icon>
|
|
3889
|
+
{{ entry.link.label }}
|
|
3028
3890
|
</a>
|
|
3029
3891
|
</nav>
|
|
3030
3892
|
|
|
@@ -3074,7 +3936,7 @@ class PraxisTabs {
|
|
|
3074
3936
|
[fitInkBarToContent]="config?.group?.fitInkBarToContent"
|
|
3075
3937
|
[headerPosition]="config?.group?.headerPosition ?? 'above'"
|
|
3076
3938
|
[preserveContent]="config?.group?.preserveContent"
|
|
3077
|
-
[selectedIndex]="
|
|
3939
|
+
[selectedIndex]="selectedVisibleTabIndex()"
|
|
3078
3940
|
[mat-stretch-tabs]="config?.group?.stretchTabs"
|
|
3079
3941
|
[mat-align-tabs]="config?.group?.alignTabs || 'start'"
|
|
3080
3942
|
[color]="config?.group?.color"
|
|
@@ -3084,26 +3946,27 @@ class PraxisTabs {
|
|
|
3084
3946
|
[attr.aria-labelledby]="config?.group?.ariaLabelledby || null"
|
|
3085
3947
|
(animationDone)="animationDone.emit()"
|
|
3086
3948
|
(focusChange)="focusChange.emit($event)"
|
|
3087
|
-
(selectedIndexChange)="
|
|
3949
|
+
(selectedIndexChange)="onVisibleTabIndexChange($event)"
|
|
3088
3950
|
(selectedTabChange)="selectedTabChange.emit($event)"
|
|
3089
3951
|
class="praxis-tabs-group"
|
|
3090
3952
|
>
|
|
3091
3953
|
<mat-tab
|
|
3092
|
-
*ngFor="let
|
|
3093
|
-
[disabled]="tab.disabled"
|
|
3094
|
-
[labelClass]="tab.labelClass ?? ''"
|
|
3095
|
-
[bodyClass]="tab.bodyClass ?? ''"
|
|
3096
|
-
[id]="tab.id || ''"
|
|
3097
|
-
[attr.aria-label]="tab.ariaLabel || null"
|
|
3098
|
-
[attr.aria-labelledby]="tab.ariaLabelledby || null"
|
|
3954
|
+
*ngFor="let entry of visibleTabEntries(); let i = index; trackBy: trackVisibleTab"
|
|
3955
|
+
[disabled]="entry.tab.disabled"
|
|
3956
|
+
[labelClass]="entry.tab.labelClass ?? ''"
|
|
3957
|
+
[bodyClass]="entry.tab.bodyClass ?? ''"
|
|
3958
|
+
[id]="entry.tab.id || ''"
|
|
3959
|
+
[attr.aria-label]="entry.tab.ariaLabel || null"
|
|
3960
|
+
[attr.aria-labelledby]="entry.tab.ariaLabelledby || null"
|
|
3099
3961
|
>
|
|
3100
3962
|
<ng-template mat-tab-label>
|
|
3101
|
-
<
|
|
3963
|
+
<mat-icon *ngIf="entry.tab.icon" class="tab-label-icon" [praxisIcon]="entry.tab.icon"></mat-icon>
|
|
3964
|
+
<span>{{ entry.tab.textLabel }}</span>
|
|
3102
3965
|
<button
|
|
3103
3966
|
*ngIf="config?.behavior?.closeable"
|
|
3104
3967
|
mat-icon-button
|
|
3105
3968
|
type="button"
|
|
3106
|
-
(click)="closeTab(
|
|
3969
|
+
(click)="closeTab(entry.index); $event.stopPropagation()"
|
|
3107
3970
|
(keydown.enter)="$event.stopPropagation()"
|
|
3108
3971
|
(keydown.space)="$event.stopPropagation()"
|
|
3109
3972
|
[attr.aria-label]="t('chrome.closeTab', 'Fechar aba')"
|
|
@@ -3114,10 +3977,10 @@ class PraxisTabs {
|
|
|
3114
3977
|
<button
|
|
3115
3978
|
mat-icon-button
|
|
3116
3979
|
type="button"
|
|
3117
|
-
(click)="moveTab(
|
|
3980
|
+
(click)="moveTab(entry.index, -1); $event.stopPropagation()"
|
|
3118
3981
|
(keydown.enter)="$event.stopPropagation()"
|
|
3119
3982
|
(keydown.space)="$event.stopPropagation()"
|
|
3120
|
-
[disabled]="
|
|
3983
|
+
[disabled]="entry.index===0"
|
|
3121
3984
|
[attr.aria-label]="t('chrome.moveTabLeft', 'Mover aba para esquerda')"
|
|
3122
3985
|
>
|
|
3123
3986
|
<mat-icon fontIcon="arrow_back"></mat-icon>
|
|
@@ -3125,10 +3988,10 @@ class PraxisTabs {
|
|
|
3125
3988
|
<button
|
|
3126
3989
|
mat-icon-button
|
|
3127
3990
|
type="button"
|
|
3128
|
-
(click)="moveTab(
|
|
3991
|
+
(click)="moveTab(entry.index, 1); $event.stopPropagation()"
|
|
3129
3992
|
(keydown.enter)="$event.stopPropagation()"
|
|
3130
3993
|
(keydown.space)="$event.stopPropagation()"
|
|
3131
|
-
[disabled]="
|
|
3994
|
+
[disabled]="entry.index===(config?.tabs?.length||1)-1"
|
|
3132
3995
|
[attr.aria-label]="t('chrome.moveTabRight', 'Mover aba para direita')"
|
|
3133
3996
|
>
|
|
3134
3997
|
<mat-icon fontIcon="arrow_forward"></mat-icon>
|
|
@@ -3137,20 +4000,20 @@ class PraxisTabs {
|
|
|
3137
4000
|
</ng-template>
|
|
3138
4001
|
|
|
3139
4002
|
<ng-template matTabContent>
|
|
3140
|
-
<ng-container *ngIf="(tab.content?.length || tab.widgets?.length) && groupContentReady(
|
|
3141
|
-
<ng-container *ngIf="tab.content && form">
|
|
4003
|
+
<ng-container *ngIf="(entry.tab.content?.length || entry.tab.widgets?.length) && groupContentReady(entry.index); else emptyTab">
|
|
4004
|
+
<ng-container *ngIf="entry.tab.content && form">
|
|
3142
4005
|
<ng-container
|
|
3143
4006
|
dynamicFieldLoader
|
|
3144
|
-
[fields]="tab.content || []"
|
|
4007
|
+
[fields]="entry.tab.content || []"
|
|
3145
4008
|
[formGroup]="form!"
|
|
3146
4009
|
></ng-container>
|
|
3147
4010
|
</ng-container>
|
|
3148
|
-
<ng-container *ngIf="tab.widgets?.length">
|
|
3149
|
-
<ng-container *ngFor="let w of tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
4011
|
+
<ng-container *ngIf="entry.tab.widgets?.length">
|
|
4012
|
+
<ng-container *ngFor="let w of entry.tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
3150
4013
|
<ng-container
|
|
3151
4014
|
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
3152
4015
|
[context]="context || {}"
|
|
3153
|
-
(widgetEvent)="emitWidgetEvent(tabEventPath(tab.id,
|
|
4016
|
+
(widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
|
|
3154
4017
|
></ng-container>
|
|
3155
4018
|
</ng-container>
|
|
3156
4019
|
</ng-container>
|
|
@@ -3191,7 +4054,7 @@ class PraxisTabs {
|
|
|
3191
4054
|
<mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
|
|
3192
4055
|
</button>
|
|
3193
4056
|
</div>
|
|
3194
|
-
`, 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}.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}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type:
|
|
4057
|
+
`, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3195
4058
|
}
|
|
3196
4059
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, decorators: [{
|
|
3197
4060
|
type: Component,
|
|
@@ -3207,7 +4070,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3207
4070
|
EmptyStateCardComponent,
|
|
3208
4071
|
DynamicFieldLoaderDirective,
|
|
3209
4072
|
DynamicWidgetLoaderDirective,
|
|
3210
|
-
|
|
4073
|
+
PraxisAiAssistantShellComponent,
|
|
3211
4074
|
], template: `
|
|
3212
4075
|
<div
|
|
3213
4076
|
class="praxis-tabs-root"
|
|
@@ -3225,9 +4088,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3225
4088
|
<style *ngIf="styleCss() as s" [innerHTML]="s"></style>
|
|
3226
4089
|
|
|
3227
4090
|
<div class="tabs-ai-assistant" *ngIf="enableCustomization">
|
|
3228
|
-
<
|
|
4091
|
+
<button
|
|
4092
|
+
mat-mini-fab
|
|
4093
|
+
type="button"
|
|
4094
|
+
color="primary"
|
|
4095
|
+
class="tabs-ai-assistant-trigger"
|
|
4096
|
+
(click)="openAiAssistant()"
|
|
4097
|
+
matTooltip="Copiloto semantico Praxis"
|
|
4098
|
+
aria-label="Abrir copiloto semantico Praxis das abas"
|
|
4099
|
+
data-testid="praxis-tabs-ai-assistant-trigger"
|
|
4100
|
+
>
|
|
4101
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
4102
|
+
</button>
|
|
3229
4103
|
</div>
|
|
3230
4104
|
|
|
4105
|
+
<praxis-ai-assistant-shell
|
|
4106
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
4107
|
+
[labels]="aiAssistantLabels"
|
|
4108
|
+
[mode]="aiAssistantViewState.mode"
|
|
4109
|
+
[state]="aiAssistantViewState.state"
|
|
4110
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
4111
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
4112
|
+
[messages]="aiAssistantViewState.messages"
|
|
4113
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
4114
|
+
[prompt]="aiAssistantPrompt"
|
|
4115
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
4116
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
4117
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
4118
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
4119
|
+
[layout]="aiAssistantLayout"
|
|
4120
|
+
testIdPrefix="praxis-tabs-ai-assistant"
|
|
4121
|
+
panelTestId="praxis-tabs-ai-assistant-panel"
|
|
4122
|
+
submitTestId="praxis-tabs-ai-assistant-submit"
|
|
4123
|
+
applyTestId="praxis-tabs-ai-assistant-apply"
|
|
4124
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
4125
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
4126
|
+
(apply)="onAiAssistantApply()"
|
|
4127
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
4128
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
4129
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
4130
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
4131
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
4132
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
4133
|
+
(close)="closeAiAssistant()"
|
|
4134
|
+
></praxis-ai-assistant-shell>
|
|
4135
|
+
|
|
3231
4136
|
<!-- Empty state (global) -->
|
|
3232
4137
|
<ng-container *ngIf="isEmptyGlobal(); else notEmpty">
|
|
3233
4138
|
<praxis-empty-state-card
|
|
@@ -3251,13 +4156,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3251
4156
|
cdkDropList
|
|
3252
4157
|
cdkDropListOrientation="horizontal"
|
|
3253
4158
|
[cdkDropListDisabled]="!config?.behavior?.reorderable"
|
|
3254
|
-
(cdkDropListDropped)="
|
|
4159
|
+
(cdkDropListDropped)="onVisibleNavDrop($event)"
|
|
3255
4160
|
[disablePagination]="config?.nav?.disablePagination"
|
|
3256
4161
|
[fitInkBarToContent]="config?.nav?.fitInkBarToContent"
|
|
3257
4162
|
[mat-stretch-tabs]="config?.nav?.stretchTabs"
|
|
3258
4163
|
[color]="config?.nav?.color"
|
|
3259
4164
|
[backgroundColor]="config?.nav?.backgroundColor"
|
|
3260
|
-
[selectedIndex]="
|
|
4165
|
+
[selectedIndex]="selectedVisibleNavIndex()"
|
|
3261
4166
|
[attr.aria-label]="config?.nav?.ariaLabel || config?.group?.ariaLabel || null"
|
|
3262
4167
|
[attr.aria-labelledby]="config?.nav?.ariaLabelledby || config?.group?.ariaLabelledby || null"
|
|
3263
4168
|
[animationDuration]="effectiveAnimationDuration()"
|
|
@@ -3266,21 +4171,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3266
4171
|
>
|
|
3267
4172
|
<a
|
|
3268
4173
|
mat-tab-link
|
|
3269
|
-
*ngFor="let
|
|
4174
|
+
*ngFor="let entry of visibleNavLinkEntries(); let i = index; trackBy: trackVisibleNavLink"
|
|
3270
4175
|
cdkDrag
|
|
3271
4176
|
[cdkDragDisabled]="!config?.behavior?.reorderable"
|
|
3272
4177
|
cdkDragLockAxis="x"
|
|
3273
|
-
[active]="getNavActive(
|
|
3274
|
-
[disabled]="link.disabled"
|
|
3275
|
-
[disableRipple]="config?.nav?.disableRipple || link.disableRipple"
|
|
3276
|
-
[fitInkBarToContent]="link.fitInkBarToContent || false"
|
|
3277
|
-
[id]="link.id || ''"
|
|
3278
|
-
(click)="onNavClick(
|
|
4178
|
+
[active]="getNavActive(entry.index)"
|
|
4179
|
+
[disabled]="entry.link.disabled"
|
|
4180
|
+
[disableRipple]="config?.nav?.disableRipple || entry.link.disableRipple"
|
|
4181
|
+
[fitInkBarToContent]="entry.link.fitInkBarToContent || false"
|
|
4182
|
+
[id]="entry.link.id || ''"
|
|
4183
|
+
(click)="onNavClick(entry.index)"
|
|
3279
4184
|
>
|
|
3280
4185
|
<span *ngIf="config?.behavior?.reorderable" class="drag-handle" cdkDragHandle>
|
|
3281
4186
|
<mat-icon fontIcon="drag_indicator"></mat-icon>
|
|
3282
4187
|
</span>
|
|
3283
|
-
|
|
4188
|
+
<mat-icon *ngIf="entry.link.icon" class="tab-label-icon" [praxisIcon]="entry.link.icon"></mat-icon>
|
|
4189
|
+
{{ entry.link.label }}
|
|
3284
4190
|
</a>
|
|
3285
4191
|
</nav>
|
|
3286
4192
|
|
|
@@ -3330,7 +4236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3330
4236
|
[fitInkBarToContent]="config?.group?.fitInkBarToContent"
|
|
3331
4237
|
[headerPosition]="config?.group?.headerPosition ?? 'above'"
|
|
3332
4238
|
[preserveContent]="config?.group?.preserveContent"
|
|
3333
|
-
[selectedIndex]="
|
|
4239
|
+
[selectedIndex]="selectedVisibleTabIndex()"
|
|
3334
4240
|
[mat-stretch-tabs]="config?.group?.stretchTabs"
|
|
3335
4241
|
[mat-align-tabs]="config?.group?.alignTabs || 'start'"
|
|
3336
4242
|
[color]="config?.group?.color"
|
|
@@ -3340,26 +4246,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3340
4246
|
[attr.aria-labelledby]="config?.group?.ariaLabelledby || null"
|
|
3341
4247
|
(animationDone)="animationDone.emit()"
|
|
3342
4248
|
(focusChange)="focusChange.emit($event)"
|
|
3343
|
-
(selectedIndexChange)="
|
|
4249
|
+
(selectedIndexChange)="onVisibleTabIndexChange($event)"
|
|
3344
4250
|
(selectedTabChange)="selectedTabChange.emit($event)"
|
|
3345
4251
|
class="praxis-tabs-group"
|
|
3346
4252
|
>
|
|
3347
4253
|
<mat-tab
|
|
3348
|
-
*ngFor="let
|
|
3349
|
-
[disabled]="tab.disabled"
|
|
3350
|
-
[labelClass]="tab.labelClass ?? ''"
|
|
3351
|
-
[bodyClass]="tab.bodyClass ?? ''"
|
|
3352
|
-
[id]="tab.id || ''"
|
|
3353
|
-
[attr.aria-label]="tab.ariaLabel || null"
|
|
3354
|
-
[attr.aria-labelledby]="tab.ariaLabelledby || null"
|
|
4254
|
+
*ngFor="let entry of visibleTabEntries(); let i = index; trackBy: trackVisibleTab"
|
|
4255
|
+
[disabled]="entry.tab.disabled"
|
|
4256
|
+
[labelClass]="entry.tab.labelClass ?? ''"
|
|
4257
|
+
[bodyClass]="entry.tab.bodyClass ?? ''"
|
|
4258
|
+
[id]="entry.tab.id || ''"
|
|
4259
|
+
[attr.aria-label]="entry.tab.ariaLabel || null"
|
|
4260
|
+
[attr.aria-labelledby]="entry.tab.ariaLabelledby || null"
|
|
3355
4261
|
>
|
|
3356
4262
|
<ng-template mat-tab-label>
|
|
3357
|
-
<
|
|
4263
|
+
<mat-icon *ngIf="entry.tab.icon" class="tab-label-icon" [praxisIcon]="entry.tab.icon"></mat-icon>
|
|
4264
|
+
<span>{{ entry.tab.textLabel }}</span>
|
|
3358
4265
|
<button
|
|
3359
4266
|
*ngIf="config?.behavior?.closeable"
|
|
3360
4267
|
mat-icon-button
|
|
3361
4268
|
type="button"
|
|
3362
|
-
(click)="closeTab(
|
|
4269
|
+
(click)="closeTab(entry.index); $event.stopPropagation()"
|
|
3363
4270
|
(keydown.enter)="$event.stopPropagation()"
|
|
3364
4271
|
(keydown.space)="$event.stopPropagation()"
|
|
3365
4272
|
[attr.aria-label]="t('chrome.closeTab', 'Fechar aba')"
|
|
@@ -3370,10 +4277,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3370
4277
|
<button
|
|
3371
4278
|
mat-icon-button
|
|
3372
4279
|
type="button"
|
|
3373
|
-
(click)="moveTab(
|
|
4280
|
+
(click)="moveTab(entry.index, -1); $event.stopPropagation()"
|
|
3374
4281
|
(keydown.enter)="$event.stopPropagation()"
|
|
3375
4282
|
(keydown.space)="$event.stopPropagation()"
|
|
3376
|
-
[disabled]="
|
|
4283
|
+
[disabled]="entry.index===0"
|
|
3377
4284
|
[attr.aria-label]="t('chrome.moveTabLeft', 'Mover aba para esquerda')"
|
|
3378
4285
|
>
|
|
3379
4286
|
<mat-icon fontIcon="arrow_back"></mat-icon>
|
|
@@ -3381,10 +4288,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3381
4288
|
<button
|
|
3382
4289
|
mat-icon-button
|
|
3383
4290
|
type="button"
|
|
3384
|
-
(click)="moveTab(
|
|
4291
|
+
(click)="moveTab(entry.index, 1); $event.stopPropagation()"
|
|
3385
4292
|
(keydown.enter)="$event.stopPropagation()"
|
|
3386
4293
|
(keydown.space)="$event.stopPropagation()"
|
|
3387
|
-
[disabled]="
|
|
4294
|
+
[disabled]="entry.index===(config?.tabs?.length||1)-1"
|
|
3388
4295
|
[attr.aria-label]="t('chrome.moveTabRight', 'Mover aba para direita')"
|
|
3389
4296
|
>
|
|
3390
4297
|
<mat-icon fontIcon="arrow_forward"></mat-icon>
|
|
@@ -3393,20 +4300,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3393
4300
|
</ng-template>
|
|
3394
4301
|
|
|
3395
4302
|
<ng-template matTabContent>
|
|
3396
|
-
<ng-container *ngIf="(tab.content?.length || tab.widgets?.length) && groupContentReady(
|
|
3397
|
-
<ng-container *ngIf="tab.content && form">
|
|
4303
|
+
<ng-container *ngIf="(entry.tab.content?.length || entry.tab.widgets?.length) && groupContentReady(entry.index); else emptyTab">
|
|
4304
|
+
<ng-container *ngIf="entry.tab.content && form">
|
|
3398
4305
|
<ng-container
|
|
3399
4306
|
dynamicFieldLoader
|
|
3400
|
-
[fields]="tab.content || []"
|
|
4307
|
+
[fields]="entry.tab.content || []"
|
|
3401
4308
|
[formGroup]="form!"
|
|
3402
4309
|
></ng-container>
|
|
3403
4310
|
</ng-container>
|
|
3404
|
-
<ng-container *ngIf="tab.widgets?.length">
|
|
3405
|
-
<ng-container *ngFor="let w of tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
4311
|
+
<ng-container *ngIf="entry.tab.widgets?.length">
|
|
4312
|
+
<ng-container *ngFor="let w of entry.tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
|
|
3406
4313
|
<ng-container
|
|
3407
4314
|
[dynamicWidgetLoader]="resolveWidgetDefinition(w)"
|
|
3408
4315
|
[context]="context || {}"
|
|
3409
|
-
(widgetEvent)="emitWidgetEvent(tabEventPath(tab.id,
|
|
4316
|
+
(widgetEvent)="emitWidgetEvent(tabEventPath(entry.tab.id, entry.index), $event)"
|
|
3410
4317
|
></ng-container>
|
|
3411
4318
|
</ng-container>
|
|
3412
4319
|
</ng-container>
|
|
@@ -3447,7 +4354,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3447
4354
|
<mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
|
|
3448
4355
|
</button>
|
|
3449
4356
|
</div>
|
|
3450
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.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}: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"] }]
|
|
4357
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
|
|
3451
4358
|
}], propDecorators: { config: [{
|
|
3452
4359
|
type: Input
|
|
3453
4360
|
}], tabsId: [{
|
|
@@ -3455,6 +4362,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3455
4362
|
args: [{ required: true }]
|
|
3456
4363
|
}], componentInstanceId: [{
|
|
3457
4364
|
type: Input
|
|
4365
|
+
}], selectedIndex: [{
|
|
4366
|
+
type: Input
|
|
3458
4367
|
}], enableCustomization: [{
|
|
3459
4368
|
type: Input
|
|
3460
4369
|
}], form: [{
|
|
@@ -3477,6 +4386,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3477
4386
|
type: Output
|
|
3478
4387
|
}] } });
|
|
3479
4388
|
|
|
4389
|
+
class PraxisTabsWidgetConfigEditor {
|
|
4390
|
+
set inputs(value) {
|
|
4391
|
+
this._inputs = value;
|
|
4392
|
+
this.editorDocument = this.createDocument();
|
|
4393
|
+
}
|
|
4394
|
+
get inputs() {
|
|
4395
|
+
return this._inputs;
|
|
4396
|
+
}
|
|
4397
|
+
set widgetKey(value) {
|
|
4398
|
+
this._widgetKey = value;
|
|
4399
|
+
this.editorDocument = this.createDocument();
|
|
4400
|
+
}
|
|
4401
|
+
get widgetKey() {
|
|
4402
|
+
return this._widgetKey;
|
|
4403
|
+
}
|
|
4404
|
+
tabsEditor;
|
|
4405
|
+
isDirty$ = new BehaviorSubject(false);
|
|
4406
|
+
isValid$ = new BehaviorSubject(true);
|
|
4407
|
+
isBusy$ = new BehaviorSubject(false);
|
|
4408
|
+
subscription = new Subscription();
|
|
4409
|
+
emptyConfig = {};
|
|
4410
|
+
_inputs = null;
|
|
4411
|
+
_widgetKey;
|
|
4412
|
+
editorDocument = this.createDocument();
|
|
4413
|
+
get config() {
|
|
4414
|
+
return this.inputs?.config ?? this.emptyConfig;
|
|
4415
|
+
}
|
|
4416
|
+
get tabsId() {
|
|
4417
|
+
return this.inputs?.tabsId ?? this.widgetKey;
|
|
4418
|
+
}
|
|
4419
|
+
get componentInstanceId() {
|
|
4420
|
+
return this.inputs?.componentInstanceId ?? undefined;
|
|
4421
|
+
}
|
|
4422
|
+
ngAfterViewInit() {
|
|
4423
|
+
if (!this.tabsEditor) {
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
this.subscription.add(this.tabsEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
|
|
4427
|
+
this.subscription.add(this.tabsEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
|
|
4428
|
+
this.subscription.add(this.tabsEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
|
|
4429
|
+
}
|
|
4430
|
+
ngOnDestroy() {
|
|
4431
|
+
this.subscription.unsubscribe();
|
|
4432
|
+
}
|
|
4433
|
+
getSettingsValue() {
|
|
4434
|
+
return this.buildValue(this.tabsEditor?.getSettingsValue());
|
|
4435
|
+
}
|
|
4436
|
+
onSave() {
|
|
4437
|
+
return this.buildValue(this.tabsEditor?.onSave?.() ?? this.tabsEditor?.getSettingsValue());
|
|
4438
|
+
}
|
|
4439
|
+
reset() {
|
|
4440
|
+
this.tabsEditor?.reset();
|
|
4441
|
+
}
|
|
4442
|
+
buildValue(document) {
|
|
4443
|
+
const bindings = document?.bindings ?? {};
|
|
4444
|
+
return {
|
|
4445
|
+
inputs: {
|
|
4446
|
+
...(this.inputs ?? {}),
|
|
4447
|
+
config: document?.config ?? this.config,
|
|
4448
|
+
...(bindings.tabsId ? { tabsId: bindings.tabsId } : this.tabsId ? { tabsId: this.tabsId } : {}),
|
|
4449
|
+
...(bindings.componentInstanceId ? { componentInstanceId: bindings.componentInstanceId } : {}),
|
|
4450
|
+
},
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
createDocument() {
|
|
4454
|
+
return createTabsAuthoringDocument({
|
|
4455
|
+
config: this.config,
|
|
4456
|
+
bindings: {
|
|
4457
|
+
tabsId: this.tabsId,
|
|
4458
|
+
componentInstanceId: this.componentInstanceId,
|
|
4459
|
+
},
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4463
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsWidgetConfigEditor, isStandalone: true, selector: "praxis-tabs-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "tabsEditor", first: true, predicate: ["tabsEditor"], descendants: true }], ngImport: i0, template: `
|
|
4464
|
+
<section data-testid="tabs-widget-config-editor">
|
|
4465
|
+
<praxis-tabs-config-editor #tabsEditor [document]="editorDocument" />
|
|
4466
|
+
</section>
|
|
4467
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisTabsConfigEditor, selector: "praxis-tabs-config-editor", inputs: ["document"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4468
|
+
}
|
|
4469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsWidgetConfigEditor, decorators: [{
|
|
4470
|
+
type: Component,
|
|
4471
|
+
args: [{
|
|
4472
|
+
selector: 'praxis-tabs-widget-config-editor',
|
|
4473
|
+
standalone: true,
|
|
4474
|
+
imports: [CommonModule, PraxisTabsConfigEditor],
|
|
4475
|
+
template: `
|
|
4476
|
+
<section data-testid="tabs-widget-config-editor">
|
|
4477
|
+
<praxis-tabs-config-editor #tabsEditor [document]="editorDocument" />
|
|
4478
|
+
</section>
|
|
4479
|
+
`,
|
|
4480
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4481
|
+
}]
|
|
4482
|
+
}], propDecorators: { inputs: [{
|
|
4483
|
+
type: Input
|
|
4484
|
+
}], widgetKey: [{
|
|
4485
|
+
type: Input
|
|
4486
|
+
}], tabsEditor: [{
|
|
4487
|
+
type: ViewChild,
|
|
4488
|
+
args: ['tabsEditor']
|
|
4489
|
+
}] } });
|
|
4490
|
+
|
|
3480
4491
|
const PRAXIS_TABS_PORTS = [
|
|
3481
4492
|
{
|
|
3482
4493
|
id: 'context',
|
|
@@ -3504,6 +4515,19 @@ const PRAXIS_TABS_PORTS = [
|
|
|
3504
4515
|
description: 'Fragmento canonico de configuracao das tabs/nav e dos widgets internos.',
|
|
3505
4516
|
exposure: { public: true, group: 'config' },
|
|
3506
4517
|
},
|
|
4518
|
+
{
|
|
4519
|
+
id: 'selectedIndex',
|
|
4520
|
+
label: 'Indice selecionado',
|
|
4521
|
+
direction: 'input',
|
|
4522
|
+
semanticKind: 'value',
|
|
4523
|
+
schema: {
|
|
4524
|
+
id: 'number',
|
|
4525
|
+
kind: 'ts-type',
|
|
4526
|
+
ref: 'number',
|
|
4527
|
+
},
|
|
4528
|
+
description: 'Indice ativo de abas/nav para controle externo por composicao.',
|
|
4529
|
+
exposure: { public: true, group: 'state' },
|
|
4530
|
+
},
|
|
3507
4531
|
{
|
|
3508
4532
|
id: 'selectedIndexChange',
|
|
3509
4533
|
label: 'Troca de indice selecionado',
|
|
@@ -3540,6 +4564,14 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
|
|
|
3540
4564
|
friendlyName: 'Praxis Tabs',
|
|
3541
4565
|
description: 'Abas dinâmicas baseadas em metadata, com MatTabGroup/TabNav e suporte a tokens M3 via appearance.',
|
|
3542
4566
|
icon: 'tab',
|
|
4567
|
+
authoringManifestRef: {
|
|
4568
|
+
componentId: 'praxis-tabs',
|
|
4569
|
+
source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
|
|
4570
|
+
},
|
|
4571
|
+
configEditor: {
|
|
4572
|
+
component: PraxisTabsWidgetConfigEditor,
|
|
4573
|
+
title: 'Configure tabs',
|
|
4574
|
+
},
|
|
3543
4575
|
inputs: [
|
|
3544
4576
|
{ name: 'config', type: 'TabsMetadata', label: 'Configuração', description: 'Configuração JSON (tabs/nav, aparência e widgets internos)' },
|
|
3545
4577
|
{
|
|
@@ -3554,6 +4586,12 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
|
|
|
3554
4586
|
label: 'ID da instância',
|
|
3555
4587
|
description: 'Identificador opcional para múltiplas instâncias na mesma rota',
|
|
3556
4588
|
},
|
|
4589
|
+
{
|
|
4590
|
+
name: 'selectedIndex',
|
|
4591
|
+
type: 'number',
|
|
4592
|
+
label: 'Indice selecionado',
|
|
4593
|
+
description: 'Indice ativo de abas/nav controlavel externamente pela composicao',
|
|
4594
|
+
},
|
|
3557
4595
|
{
|
|
3558
4596
|
name: 'enableCustomization',
|
|
3559
4597
|
type: 'boolean',
|
|
@@ -3675,6 +4713,341 @@ function providePraxisTabsMetadata() {
|
|
|
3675
4713
|
};
|
|
3676
4714
|
}
|
|
3677
4715
|
|
|
4716
|
+
const tabItemSchema = {
|
|
4717
|
+
type: 'object',
|
|
4718
|
+
required: ['id', 'textLabel'],
|
|
4719
|
+
properties: {
|
|
4720
|
+
id: { type: 'string' },
|
|
4721
|
+
textLabel: { type: 'string' },
|
|
4722
|
+
icon: { type: 'string' },
|
|
4723
|
+
disabled: { type: 'boolean' },
|
|
4724
|
+
visible: { type: 'boolean', default: true },
|
|
4725
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
4726
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
4727
|
+
},
|
|
4728
|
+
};
|
|
4729
|
+
const tabPatchSchema = {
|
|
4730
|
+
type: 'object',
|
|
4731
|
+
minProperties: 1,
|
|
4732
|
+
properties: {
|
|
4733
|
+
id: { type: 'string' },
|
|
4734
|
+
textLabel: { type: 'string' },
|
|
4735
|
+
icon: { type: 'string' },
|
|
4736
|
+
disabled: { type: 'boolean' },
|
|
4737
|
+
visible: { type: 'boolean' },
|
|
4738
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
4739
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
4740
|
+
},
|
|
4741
|
+
};
|
|
4742
|
+
const PRAXIS_TABS_AUTHORING_MANIFEST = {
|
|
4743
|
+
schemaVersion: '1.0.0',
|
|
4744
|
+
componentId: 'praxis-tabs',
|
|
4745
|
+
ownerPackage: '@praxisui/tabs',
|
|
4746
|
+
configSchemaId: 'TabsMetadata',
|
|
4747
|
+
manifestVersion: '1.0.0',
|
|
4748
|
+
runtimeInputs: [
|
|
4749
|
+
{ name: 'config', type: 'TabsMetadata', description: 'Canonical tabs/nav configuration.' },
|
|
4750
|
+
{ name: 'tabsId', type: 'string', description: 'Stable id used to derive persistence scope.' },
|
|
4751
|
+
{ name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
|
|
4752
|
+
{ name: 'form', type: 'FormGroup', description: 'FormGroup consumed by dynamic field content.' },
|
|
4753
|
+
{ name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
|
|
4754
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
|
|
4755
|
+
],
|
|
4756
|
+
editableTargets: [
|
|
4757
|
+
{ kind: 'tab', resolver: 'tab-by-id-or-label', description: 'A group-mode tab in config.tabs[].' },
|
|
4758
|
+
{ kind: 'tabLabel', resolver: 'tab-by-id-or-label', description: 'The text label of a group-mode tab.' },
|
|
4759
|
+
{ kind: 'tabIcon', resolver: 'tab-by-id-or-label', description: 'Icon metadata rendered in a group tab label.' },
|
|
4760
|
+
{ kind: 'tabContent', resolver: 'tab-or-link-by-id', description: 'Dynamic fields or widgets hosted by a tab or nav link.' },
|
|
4761
|
+
{ kind: 'activeTab', resolver: 'tab-index-or-id', description: 'Selected tab or nav link index.' },
|
|
4762
|
+
{ kind: 'visibility', resolver: 'tab-or-link-by-id', description: 'Runtime visibility flag for a group tab or nav link.' },
|
|
4763
|
+
{ kind: 'disabledState', resolver: 'tab-or-link-by-id', description: 'Disabled state of a tab or nav link.' },
|
|
4764
|
+
{ kind: 'layout', resolver: 'tabs-layout-config', description: 'Group/nav mode, header position, density, stretch and behavior settings.' },
|
|
4765
|
+
],
|
|
4766
|
+
operations: [
|
|
4767
|
+
{
|
|
4768
|
+
operationId: 'tab.add',
|
|
4769
|
+
title: 'Add tab',
|
|
4770
|
+
scope: 'global',
|
|
4771
|
+
targetKind: 'tab',
|
|
4772
|
+
target: { kind: 'tab', resolver: 'tabs-array', ambiguityPolicy: 'fail', required: false },
|
|
4773
|
+
inputSchema: tabItemSchema,
|
|
4774
|
+
effects: [{ kind: 'append-unique', path: 'tabs[]', key: 'id' }],
|
|
4775
|
+
validators: ['tab-id-unique', 'tabs-mode-compatible', 'tab-content-valid'],
|
|
4776
|
+
destructive: false,
|
|
4777
|
+
requiresConfirmation: false,
|
|
4778
|
+
affectedPaths: ['tabs[]'],
|
|
4779
|
+
submissionImpact: 'config-only',
|
|
4780
|
+
preconditions: ['config-initialized'],
|
|
4781
|
+
},
|
|
4782
|
+
{
|
|
4783
|
+
operationId: 'tab.remove',
|
|
4784
|
+
title: 'Remove tab',
|
|
4785
|
+
scope: 'layout',
|
|
4786
|
+
targetKind: 'tab',
|
|
4787
|
+
target: { kind: 'tab', resolver: 'tab-by-id-or-label', ambiguityPolicy: 'fail', required: true },
|
|
4788
|
+
inputSchema: {
|
|
4789
|
+
type: 'object',
|
|
4790
|
+
properties: {
|
|
4791
|
+
replacementActiveTabId: { type: 'string' },
|
|
4792
|
+
},
|
|
4793
|
+
},
|
|
4794
|
+
effects: [
|
|
4795
|
+
{
|
|
4796
|
+
kind: 'compile-domain-patch',
|
|
4797
|
+
handler: 'tabs.remove-tab-and-reselect',
|
|
4798
|
+
handlerContract: {
|
|
4799
|
+
reads: ['tabs[]', 'group.selectedIndex'],
|
|
4800
|
+
writes: ['tabs[]', 'group.selectedIndex'],
|
|
4801
|
+
identityKeys: ['tabs[].id'],
|
|
4802
|
+
inputSchema: {
|
|
4803
|
+
type: 'object',
|
|
4804
|
+
properties: { replacementActiveTabId: { type: 'string' } },
|
|
4805
|
+
},
|
|
4806
|
+
failureModes: ['target-tab-missing', 'replacement-tab-missing', 'confirmation-missing'],
|
|
4807
|
+
description: 'Removes the target tab by stable id and reselects a safe replacement when the active/default tab is removed.',
|
|
4808
|
+
},
|
|
4809
|
+
},
|
|
4810
|
+
],
|
|
4811
|
+
destructive: true,
|
|
4812
|
+
requiresConfirmation: true,
|
|
4813
|
+
validators: ['tab-exists', 'active-tab-removal-safe', 'tab-content-removal-confirmed'],
|
|
4814
|
+
affectedPaths: ['tabs[]', 'group.selectedIndex'],
|
|
4815
|
+
submissionImpact: 'config-only',
|
|
4816
|
+
preconditions: ['config-initialized', 'target-tab-exists', 'confirmation-collected'],
|
|
4817
|
+
},
|
|
4818
|
+
{
|
|
4819
|
+
operationId: 'tab.label.set',
|
|
4820
|
+
title: 'Set tab label',
|
|
4821
|
+
scope: 'layout',
|
|
4822
|
+
targetKind: 'tabLabel',
|
|
4823
|
+
target: { kind: 'tabLabel', resolver: 'tab-by-id-or-label', ambiguityPolicy: 'fail', required: true },
|
|
4824
|
+
inputSchema: { type: 'object', required: ['textLabel'], properties: { textLabel: { type: 'string' } } },
|
|
4825
|
+
effects: [{ kind: 'merge-by-key', path: 'tabs[]', key: 'id' }],
|
|
4826
|
+
destructive: false,
|
|
4827
|
+
requiresConfirmation: false,
|
|
4828
|
+
validators: ['tab-exists', 'tab-label-valid'],
|
|
4829
|
+
affectedPaths: ['tabs[].textLabel'],
|
|
4830
|
+
submissionImpact: 'config-only',
|
|
4831
|
+
preconditions: ['config-initialized', 'target-tab-exists'],
|
|
4832
|
+
},
|
|
4833
|
+
{
|
|
4834
|
+
operationId: 'tab.icon.set',
|
|
4835
|
+
title: 'Set tab icon',
|
|
4836
|
+
scope: 'layout',
|
|
4837
|
+
targetKind: 'tabIcon',
|
|
4838
|
+
target: { kind: 'tabIcon', resolver: 'tab-by-id-or-label', ambiguityPolicy: 'fail', required: true },
|
|
4839
|
+
inputSchema: { type: 'object', required: ['icon'], properties: { icon: { type: 'string' } } },
|
|
4840
|
+
effects: [{ kind: 'merge-by-key', path: 'tabs[]', key: 'id' }],
|
|
4841
|
+
destructive: false,
|
|
4842
|
+
requiresConfirmation: false,
|
|
4843
|
+
validators: ['tab-exists', 'tab-icon-valid'],
|
|
4844
|
+
affectedPaths: ['tabs[].icon'],
|
|
4845
|
+
submissionImpact: 'visual-only',
|
|
4846
|
+
preconditions: ['config-initialized', 'target-tab-exists'],
|
|
4847
|
+
},
|
|
4848
|
+
{
|
|
4849
|
+
operationId: 'tab.order.set',
|
|
4850
|
+
title: 'Reorder tabs',
|
|
4851
|
+
scope: 'layout',
|
|
4852
|
+
targetKind: 'tab',
|
|
4853
|
+
target: { kind: 'tab', resolver: 'tab-by-id-or-label', ambiguityPolicy: 'fail', required: true },
|
|
4854
|
+
inputSchema: { type: 'object', required: ['beforeTabId'], properties: { beforeTabId: { type: 'string' } } },
|
|
4855
|
+
effects: [
|
|
4856
|
+
{
|
|
4857
|
+
kind: 'compile-domain-patch',
|
|
4858
|
+
handler: 'tabs.reorder-tab-and-preserve-selection',
|
|
4859
|
+
handlerContract: {
|
|
4860
|
+
reads: ['tabs[]', 'group.selectedIndex'],
|
|
4861
|
+
writes: ['tabs[]', 'group.selectedIndex'],
|
|
4862
|
+
identityKeys: ['tabs[].id'],
|
|
4863
|
+
inputSchema: { type: 'object', required: ['beforeTabId'], properties: { beforeTabId: { type: 'string' } } },
|
|
4864
|
+
failureModes: ['target-tab-missing', 'before-tab-missing', 'unstable-tab-id'],
|
|
4865
|
+
description: 'Reorders tabs by stable id and remaps group.selectedIndex when the selected tab crosses positions.',
|
|
4866
|
+
},
|
|
4867
|
+
},
|
|
4868
|
+
],
|
|
4869
|
+
destructive: false,
|
|
4870
|
+
requiresConfirmation: false,
|
|
4871
|
+
validators: ['tab-exists', 'tab-order-deterministic'],
|
|
4872
|
+
affectedPaths: ['tabs[]', 'group.selectedIndex'],
|
|
4873
|
+
submissionImpact: 'config-only',
|
|
4874
|
+
preconditions: ['config-initialized', 'target-tab-exists'],
|
|
4875
|
+
},
|
|
4876
|
+
{
|
|
4877
|
+
operationId: 'tab.disabled.set',
|
|
4878
|
+
title: 'Set tab disabled state',
|
|
4879
|
+
scope: 'interaction',
|
|
4880
|
+
targetKind: 'disabledState',
|
|
4881
|
+
target: { kind: 'disabledState', resolver: 'tab-or-link-by-id', ambiguityPolicy: 'fail', required: true },
|
|
4882
|
+
inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
|
|
4883
|
+
effects: [
|
|
4884
|
+
{
|
|
4885
|
+
kind: 'compile-domain-patch',
|
|
4886
|
+
handler: 'tabs.set-tab-or-link-disabled',
|
|
4887
|
+
handlerContract: {
|
|
4888
|
+
reads: ['tabs[]', 'nav.links[]', 'group.selectedIndex', 'nav.selectedIndex'],
|
|
4889
|
+
writes: ['tabs[].disabled', 'nav.links[].disabled'],
|
|
4890
|
+
identityKeys: ['tabs[].id', 'nav.links[].id'],
|
|
4891
|
+
inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
|
|
4892
|
+
failureModes: ['target-tab-or-link-missing', 'ambiguous-target', 'active-item-disabled-without-reselection'],
|
|
4893
|
+
description: 'Sets disabled on the resolved group tab or nav link without guessing between modes.',
|
|
4894
|
+
},
|
|
4895
|
+
},
|
|
4896
|
+
],
|
|
4897
|
+
destructive: false,
|
|
4898
|
+
requiresConfirmation: false,
|
|
4899
|
+
validators: ['tab-or-link-exists', 'active-tab-disabled-safe'],
|
|
4900
|
+
affectedPaths: ['tabs[].disabled', 'nav.links[].disabled'],
|
|
4901
|
+
submissionImpact: 'config-only',
|
|
4902
|
+
preconditions: ['config-initialized', 'target-tab-or-link-exists'],
|
|
4903
|
+
},
|
|
4904
|
+
{
|
|
4905
|
+
operationId: 'tab.visible.set',
|
|
4906
|
+
title: 'Set tab visibility',
|
|
4907
|
+
scope: 'interaction',
|
|
4908
|
+
targetKind: 'visibility',
|
|
4909
|
+
target: { kind: 'visibility', resolver: 'tab-or-link-by-id', ambiguityPolicy: 'fail', required: true },
|
|
4910
|
+
inputSchema: { type: 'object', required: ['visible'], properties: { visible: { type: 'boolean' } } },
|
|
4911
|
+
effects: [
|
|
4912
|
+
{
|
|
4913
|
+
kind: 'compile-domain-patch',
|
|
4914
|
+
handler: 'tabs.set-tab-or-link-visible',
|
|
4915
|
+
handlerContract: {
|
|
4916
|
+
reads: ['tabs[]', 'nav.links[]', 'group.selectedIndex', 'nav.selectedIndex'],
|
|
4917
|
+
writes: ['tabs[].visible', 'nav.links[].visible'],
|
|
4918
|
+
identityKeys: ['tabs[].id', 'nav.links[].id'],
|
|
4919
|
+
inputSchema: { type: 'object', required: ['visible'], properties: { visible: { type: 'boolean' } } },
|
|
4920
|
+
failureModes: ['target-tab-or-link-missing', 'ambiguous-target', 'active-item-hidden-without-reselection'],
|
|
4921
|
+
description: 'Sets visible on the resolved group tab or nav link and preserves deterministic visible-index mapping.',
|
|
4922
|
+
},
|
|
4923
|
+
},
|
|
4924
|
+
],
|
|
4925
|
+
destructive: false,
|
|
4926
|
+
requiresConfirmation: false,
|
|
4927
|
+
validators: ['tab-or-link-exists', 'active-tab-visibility-safe'],
|
|
4928
|
+
affectedPaths: ['tabs[].visible', 'nav.links[].visible'],
|
|
4929
|
+
submissionImpact: 'config-only',
|
|
4930
|
+
preconditions: ['config-initialized', 'target-tab-or-link-exists'],
|
|
4931
|
+
},
|
|
4932
|
+
{
|
|
4933
|
+
operationId: 'tab.active.set',
|
|
4934
|
+
title: 'Set active tab',
|
|
4935
|
+
scope: 'interaction',
|
|
4936
|
+
targetKind: 'activeTab',
|
|
4937
|
+
target: { kind: 'activeTab', resolver: 'tab-index-or-id', ambiguityPolicy: 'fail', required: true },
|
|
4938
|
+
inputSchema: { type: 'object', required: ['selectedIndex'], properties: { selectedIndex: { type: 'number' }, tabId: { type: 'string' } } },
|
|
4939
|
+
effects: [
|
|
4940
|
+
{
|
|
4941
|
+
kind: 'compile-domain-patch',
|
|
4942
|
+
handler: 'tabs.set-active-item',
|
|
4943
|
+
handlerContract: {
|
|
4944
|
+
reads: ['tabs[]', 'nav.links[]', 'group.selectedIndex', 'nav.selectedIndex'],
|
|
4945
|
+
writes: ['group.selectedIndex', 'nav.selectedIndex'],
|
|
4946
|
+
identityKeys: ['tabs[].id', 'nav.links[].id'],
|
|
4947
|
+
inputSchema: { type: 'object', required: ['selectedIndex'], properties: { selectedIndex: { type: 'number' }, tabId: { type: 'string' } } },
|
|
4948
|
+
failureModes: ['target-tab-or-link-missing', 'selected-index-out-of-range', 'hidden-or-disabled-target'],
|
|
4949
|
+
description: 'Sets the active index for the current primary mode using either selectedIndex or a resolved tab/link id.',
|
|
4950
|
+
},
|
|
4951
|
+
},
|
|
4952
|
+
],
|
|
4953
|
+
destructive: false,
|
|
4954
|
+
requiresConfirmation: false,
|
|
4955
|
+
validators: ['active-tab-exists', 'selected-index-in-range'],
|
|
4956
|
+
affectedPaths: ['group.selectedIndex', 'nav.selectedIndex'],
|
|
4957
|
+
submissionImpact: 'config-only',
|
|
4958
|
+
preconditions: ['config-initialized', 'target-tab-or-link-exists'],
|
|
4959
|
+
},
|
|
4960
|
+
{
|
|
4961
|
+
operationId: 'layout.variant.set',
|
|
4962
|
+
title: 'Set tabs layout variant',
|
|
4963
|
+
scope: 'layout',
|
|
4964
|
+
targetKind: 'layout',
|
|
4965
|
+
target: { kind: 'layout', resolver: 'tabs-layout-config', ambiguityPolicy: 'fail', required: true },
|
|
4966
|
+
inputSchema: {
|
|
4967
|
+
type: 'object',
|
|
4968
|
+
required: ['mode'],
|
|
4969
|
+
properties: {
|
|
4970
|
+
mode: { enum: ['group', 'nav'] },
|
|
4971
|
+
density: { enum: ['compact', 'comfortable', 'spacious'] },
|
|
4972
|
+
headerPosition: { enum: ['above', 'below'] },
|
|
4973
|
+
alignTabs: { enum: ['start', 'center', 'end'] },
|
|
4974
|
+
stretchTabs: { type: 'boolean' },
|
|
4975
|
+
lazyLoad: { type: 'boolean' },
|
|
4976
|
+
},
|
|
4977
|
+
},
|
|
4978
|
+
effects: [{ kind: 'merge-object', path: 'appearance' }, { kind: 'merge-object', path: 'group' }, { kind: 'merge-object', path: 'nav' }, { kind: 'merge-object', path: 'behavior' }],
|
|
4979
|
+
destructive: false,
|
|
4980
|
+
requiresConfirmation: false,
|
|
4981
|
+
validators: ['tabs-mode-compatible', 'layout-values-valid', 'editor-runtime-round-trip'],
|
|
4982
|
+
affectedPaths: ['appearance.density', 'group.headerPosition', 'group.alignTabs', 'group.stretchTabs', 'nav.stretchTabs', 'behavior.lazyLoad'],
|
|
4983
|
+
submissionImpact: 'config-only',
|
|
4984
|
+
preconditions: ['config-initialized'],
|
|
4985
|
+
},
|
|
4986
|
+
{
|
|
4987
|
+
operationId: 'tab.content.set',
|
|
4988
|
+
title: 'Set tab content',
|
|
4989
|
+
scope: 'layout',
|
|
4990
|
+
targetKind: 'tabContent',
|
|
4991
|
+
target: { kind: 'tabContent', resolver: 'tab-or-link-by-id', ambiguityPolicy: 'fail', required: true },
|
|
4992
|
+
inputSchema: tabPatchSchema,
|
|
4993
|
+
effects: [
|
|
4994
|
+
{
|
|
4995
|
+
kind: 'compile-domain-patch',
|
|
4996
|
+
handler: 'tabs.set-tab-or-link-content',
|
|
4997
|
+
handlerContract: {
|
|
4998
|
+
reads: ['tabs[]', 'nav.links[]'],
|
|
4999
|
+
writes: ['tabs[].content', 'tabs[].widgets', 'nav.links[].content', 'nav.links[].widgets'],
|
|
5000
|
+
identityKeys: ['tabs[].id', 'nav.links[].id'],
|
|
5001
|
+
inputSchema: tabPatchSchema,
|
|
5002
|
+
failureModes: ['target-tab-or-link-missing', 'invalid-dynamic-field-content', 'invalid-widget-definition'],
|
|
5003
|
+
description: 'Updates content/widgets only on the resolved group tab or nav link while preserving nested widget identity.',
|
|
5004
|
+
},
|
|
5005
|
+
},
|
|
5006
|
+
],
|
|
5007
|
+
destructive: false,
|
|
5008
|
+
requiresConfirmation: false,
|
|
5009
|
+
validators: ['tab-or-link-exists', 'tab-content-valid', 'widget-event-delegated'],
|
|
5010
|
+
affectedPaths: ['tabs[].content', 'tabs[].widgets', 'nav.links[].content', 'nav.links[].widgets'],
|
|
5011
|
+
submissionImpact: 'config-only',
|
|
5012
|
+
preconditions: ['config-initialized', 'target-tab-or-link-exists'],
|
|
5013
|
+
},
|
|
5014
|
+
],
|
|
5015
|
+
validators: [
|
|
5016
|
+
{ validatorId: 'tab-id-unique', level: 'error', code: 'PTABS001', description: 'Tab ids and nav link ids must be unique within their mode.' },
|
|
5017
|
+
{ validatorId: 'tab-exists', level: 'error', code: 'PTABS002', description: 'Target tab must exist before applying the operation.' },
|
|
5018
|
+
{ validatorId: 'tab-or-link-exists', level: 'error', code: 'PTABS003', description: 'Target must resolve to an existing group tab or nav link.' },
|
|
5019
|
+
{ validatorId: 'active-tab-exists', level: 'error', code: 'PTABS004', description: 'Active tab or nav link selection must reference an existing item.' },
|
|
5020
|
+
{ validatorId: 'selected-index-in-range', level: 'error', code: 'PTABS005', description: 'Selected index must be clamped to the target mode item count.' },
|
|
5021
|
+
{ validatorId: 'active-tab-removal-safe', level: 'error', code: 'PTABS006', description: 'Removing the active/default tab requires confirmation or a replacement active tab.' },
|
|
5022
|
+
{ validatorId: 'tab-content-removal-confirmed', level: 'error', code: 'PTABS007', description: 'Removing a tab or link with content/widgets is destructive and requires confirmation.' },
|
|
5023
|
+
{ validatorId: 'tab-label-valid', level: 'error', code: 'PTABS008', description: 'Tab labels must be non-empty text values after localization/domain projection.' },
|
|
5024
|
+
{ validatorId: 'tab-icon-valid', level: 'warning', code: 'PTABS009', description: 'Tab icon metadata must remain compatible with the icon directive and editor round-trip.' },
|
|
5025
|
+
{ validatorId: 'tab-order-deterministic', level: 'error', code: 'PTABS010', description: 'Tab ordering must use stable ids, not transient array index as identity.' },
|
|
5026
|
+
{ validatorId: 'tabs-mode-compatible', level: 'error', code: 'PTABS011', description: 'Authoring must resolve to one primary mode: group tabs or nav links.' },
|
|
5027
|
+
{ validatorId: 'layout-values-valid', level: 'error', code: 'PTABS012', description: 'Layout values must match TabsMetadata enums and runtime bindings.' },
|
|
5028
|
+
{ validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PTABS013', description: 'Settings Panel, quick setup, JSON editor and runtime must preserve ids, order and selected index.' },
|
|
5029
|
+
{ validatorId: 'active-tab-disabled-safe', level: 'warning', code: 'PTABS014', description: 'Disabling the active item should move selection or request explicit confirmation.' },
|
|
5030
|
+
{ validatorId: 'active-tab-visibility-safe', level: 'warning', code: 'PTABS015', description: 'Hiding the active item should move selection or request explicit confirmation.' },
|
|
5031
|
+
{ validatorId: 'tab-content-valid', level: 'error', code: 'PTABS016', description: 'Tab content must be valid DynamicFieldMetadata[] or WidgetDefinition[] and preserve nested widget identity.' },
|
|
5032
|
+
{ validatorId: 'widget-event-delegated', level: 'info', code: 'PTABS017', description: 'Nested widget event paths remain delegated to the tabs runtime contract and are not redefined by authoring.' },
|
|
5033
|
+
],
|
|
5034
|
+
roundTripRequirements: [
|
|
5035
|
+
'Operations must preserve stable tab/link ids; array index may be used only as a resolver fallback, never as canonical identity.',
|
|
5036
|
+
'Settings Panel, quick setup and JSON editor must round-trip through TabsAuthoringDocument without losing config or bindings.',
|
|
5037
|
+
'Group and nav modes must remain mutually explicit; authoring cannot silently mix config.tabs and nav.links as competing primary modes.',
|
|
5038
|
+
'Nested widget events remain delegated through widgetEvent path enrichment and component-port nestedPath semantics.',
|
|
5039
|
+
],
|
|
5040
|
+
examples: [
|
|
5041
|
+
{ id: 'add-overview-tab', request: 'Add an Overview tab before the details tab.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Overview' }, isPositive: true },
|
|
5042
|
+
{ id: 'rename-tab', request: 'Rename the details tab to Account Details.', operationId: 'tab.label.set', target: 'details', params: { textLabel: 'Account Details' }, isPositive: true },
|
|
5043
|
+
{ id: 'reorder-tabs', request: 'Move billing before overview.', operationId: 'tab.order.set', target: 'billing', params: { beforeTabId: 'overview' }, isPositive: true },
|
|
5044
|
+
{ 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
|
+
{ id: 'activate-tab', request: 'Open the documents tab by default.', operationId: 'tab.active.set', target: 'documents', params: { tabId: 'documents', selectedIndex: 2 }, isPositive: true },
|
|
5046
|
+
{ id: 'reject-duplicate-tab-id', request: 'Add another tab with id overview.', operationId: 'tab.add', params: { id: 'overview', textLabel: 'Duplicate Overview' }, isPositive: false },
|
|
5047
|
+
{ id: 'confirm-remove-content-tab', request: 'Remove the details tab that contains widgets.', operationId: 'tab.remove', target: 'details', params: { replacementActiveTabId: 'overview' }, isPositive: true },
|
|
5048
|
+
],
|
|
5049
|
+
};
|
|
5050
|
+
|
|
3678
5051
|
/*
|
|
3679
5052
|
* Public API Surface of praxis-tabs
|
|
3680
5053
|
*/
|
|
@@ -3683,4 +5056,4 @@ function providePraxisTabsMetadata() {
|
|
|
3683
5056
|
* Generated bundle index. Do not edit.
|
|
3684
5057
|
*/
|
|
3685
5058
|
|
|
3686
|
-
export { PRAXIS_TABS_COMPONENT_METADATA, PRAXIS_TABS_I18N_CONFIG, PRAXIS_TABS_I18N_NAMESPACE, PraxisTabs, PraxisTabsConfigEditor, TABS_AI_CAPABILITIES, buildTabsApplyPlan, createPraxisTabsI18nConfig, createTabsAuthoringDocument, normalizeTabsAuthoringDocument, providePraxisTabsI18n, providePraxisTabsMetadata, serializeTabsAuthoringDocument, toCanonicalTabsConfig, validateTabsAuthoringDocument };
|
|
5059
|
+
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 };
|