@praxisui/expansion 8.0.0-beta.3 → 8.0.0-beta.30
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 +63 -1
- package/fesm2022/praxisui-expansion.mjs +1156 -19
- package/index.d.ts +81 -6
- package/package.json +8 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
|
+
import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, effect, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
5
5
|
import { ActivatedRoute } from '@angular/router';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
7
|
import * as i2 from '@angular/material/expansion';
|
|
@@ -21,9 +21,9 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|
|
21
21
|
import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
|
|
22
22
|
import { deepMerge, ASYNC_CONFIG_STORAGE, DynamicFormService, ComponentKeyService, DynamicWidgetLoaderDirective, PraxisIconDirective, ComponentMetadataRegistry } from '@praxisui/core';
|
|
23
23
|
import { SettingsPanelService, SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
24
|
-
import { BehaviorSubject } from 'rxjs';
|
|
24
|
+
import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
|
|
25
25
|
import { take } from 'rxjs/operators';
|
|
26
|
-
import { BaseAiAdapter,
|
|
26
|
+
import { BaseAiAdapter, shouldRoutePromptToGovernedDecision, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Capabilities catalog for Praxis Expansion (ExpansionMetadata).
|
|
@@ -62,6 +62,7 @@ const CAPS = [
|
|
|
62
62
|
{ path: 'panels[].id', category: 'panels', valueKind: 'string', description: 'Panel id.' },
|
|
63
63
|
{ path: 'panels[].title', category: 'panels', valueKind: 'string', description: 'Panel title.' },
|
|
64
64
|
{ path: 'panels[].description', category: 'panels', valueKind: 'string', description: 'Panel description.' },
|
|
65
|
+
{ path: 'panels[].icon', category: 'panels', valueKind: 'string', description: 'Panel header icon rendered through PraxisIconDirective.' },
|
|
65
66
|
{ path: 'panels[].disabled', category: 'panels', valueKind: 'boolean', description: 'Disable panel.' },
|
|
66
67
|
{ path: 'panels[].expanded', category: 'panels', valueKind: 'boolean', description: 'Expanded state.' },
|
|
67
68
|
{ path: 'panels[].hideToggle', category: 'panels', valueKind: 'boolean', description: 'Hide toggle icon for panel.' },
|
|
@@ -104,6 +105,8 @@ const EXPANSION_AI_CAPABILITIES = {
|
|
|
104
105
|
|
|
105
106
|
class ExpansionAiAdapter extends BaseAiAdapter {
|
|
106
107
|
expansion;
|
|
108
|
+
componentId = 'praxis-expansion';
|
|
109
|
+
componentType = 'expansion';
|
|
107
110
|
componentName = 'Praxis Expansion';
|
|
108
111
|
constructor(expansion) {
|
|
109
112
|
super();
|
|
@@ -124,6 +127,43 @@ class ExpansionAiAdapter extends BaseAiAdapter {
|
|
|
124
127
|
multi: this.expansion.config?.accordion?.multi ?? false,
|
|
125
128
|
};
|
|
126
129
|
}
|
|
130
|
+
getDataProfile() {
|
|
131
|
+
const panels = this.getCurrentConfig().panels || [];
|
|
132
|
+
return {
|
|
133
|
+
expansionId: this.expansion.expansionId,
|
|
134
|
+
panelCount: panels.length,
|
|
135
|
+
contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
|
|
136
|
+
widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
|
|
137
|
+
actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
|
|
138
|
+
multi: this.expansion.config?.accordion?.multi ?? false,
|
|
139
|
+
displayMode: this.expansion.config?.accordion?.displayMode || 'default',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
getSchemaFields() {
|
|
143
|
+
return (this.getCurrentConfig().panels || []).map((panel, index) => ({
|
|
144
|
+
name: panel.id || panel.title || `panel-${index + 1}`,
|
|
145
|
+
label: panel.title || panel.id || `Painel ${index + 1}`,
|
|
146
|
+
disabled: !!panel.disabled,
|
|
147
|
+
expanded: !!panel.expanded,
|
|
148
|
+
hasContent: Array.isArray(panel.content) && panel.content.length > 0,
|
|
149
|
+
hasWidgets: Array.isArray(panel.widgets) && panel.widgets.length > 0,
|
|
150
|
+
hasActions: Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0,
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
getAuthoringContext() {
|
|
154
|
+
return {
|
|
155
|
+
authoringManifestRef: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
156
|
+
runtimeAuthoringPolicy: {
|
|
157
|
+
mode: 'agentic-authoring',
|
|
158
|
+
enableCustomization: !!this.expansion.enableCustomization,
|
|
159
|
+
canApplyLocalPatch: false,
|
|
160
|
+
reason: 'praxis-expansion exige componentEditPlan validado pelo manifesto antes de aplicar mudancas runtime.',
|
|
161
|
+
},
|
|
162
|
+
domainCatalog: {
|
|
163
|
+
recommendedAuthoringFlow: 'component_authoring',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
127
167
|
createSnapshot() {
|
|
128
168
|
return this.getCurrentConfig();
|
|
129
169
|
}
|
|
@@ -184,6 +224,259 @@ class ExpansionAiAdapter extends BaseAiAdapter {
|
|
|
184
224
|
}
|
|
185
225
|
}
|
|
186
226
|
|
|
227
|
+
class ExpansionAgenticAuthoringTurnFlow {
|
|
228
|
+
adapter;
|
|
229
|
+
aiApi;
|
|
230
|
+
mode = 'agentic-authoring';
|
|
231
|
+
constructor(adapter, aiApi) {
|
|
232
|
+
this.adapter = adapter;
|
|
233
|
+
this.aiApi = aiApi;
|
|
234
|
+
}
|
|
235
|
+
async submit(request) {
|
|
236
|
+
const prompt = (request.prompt ?? '').trim();
|
|
237
|
+
if (!prompt)
|
|
238
|
+
return { state: 'listening', phase: 'capture', statusText: '' };
|
|
239
|
+
const componentId = this.adapter.componentId || request.componentId || 'praxis-expansion';
|
|
240
|
+
const componentType = this.adapter.componentType || request.componentType || 'expansion';
|
|
241
|
+
const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
|
|
242
|
+
const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
|
|
243
|
+
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
244
|
+
const schemaFields = this.adapter.getSchemaFields?.()
|
|
245
|
+
?.map((field) => this.toAiJsonObject(field))
|
|
246
|
+
.filter((field) => Object.keys(field).length > 0);
|
|
247
|
+
const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
|
|
248
|
+
if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
|
|
249
|
+
return this.toGovernedDecisionHandoff(prompt, request);
|
|
250
|
+
}
|
|
251
|
+
const response = await firstValueFrom(this.aiApi.getPatch({
|
|
252
|
+
componentId,
|
|
253
|
+
componentType,
|
|
254
|
+
userPrompt: prompt,
|
|
255
|
+
sessionId: request.sessionId,
|
|
256
|
+
clientTurnId: request.clientTurnId,
|
|
257
|
+
messages: this.toChatMessages(request.messages, prompt),
|
|
258
|
+
currentState,
|
|
259
|
+
currentStateDigest: this.buildCurrentStateDigest(dataProfile),
|
|
260
|
+
uiContextRef: { componentId, componentType },
|
|
261
|
+
...(dataProfile ? { dataProfile } : {}),
|
|
262
|
+
...(runtimeState ? { runtimeState } : {}),
|
|
263
|
+
...(schemaFields?.length ? { schemaFields } : {}),
|
|
264
|
+
...(contextHints ? { contextHints } : {}),
|
|
265
|
+
}));
|
|
266
|
+
return this.toTurnResult(this.compileAdapterResponse(response), request);
|
|
267
|
+
}
|
|
268
|
+
async apply(_request) {
|
|
269
|
+
return {
|
|
270
|
+
state: 'error',
|
|
271
|
+
phase: 'apply',
|
|
272
|
+
assistantMessage: 'O expansion ainda exige componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
|
|
273
|
+
errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-expansion.',
|
|
274
|
+
canApply: false,
|
|
275
|
+
pendingPatch: null,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
cancel() {
|
|
279
|
+
return Promise.resolve({
|
|
280
|
+
state: 'listening',
|
|
281
|
+
phase: 'capture',
|
|
282
|
+
assistantMessage: 'Solicitacao cancelada.',
|
|
283
|
+
statusText: '',
|
|
284
|
+
canApply: false,
|
|
285
|
+
pendingPatch: null,
|
|
286
|
+
pendingClarification: null,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
retry(request) {
|
|
290
|
+
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
291
|
+
.find((message) => message.role === 'user')?.text;
|
|
292
|
+
return this.submit({ ...request, prompt: lastPrompt ?? request.prompt, action: { kind: 'retry' } });
|
|
293
|
+
}
|
|
294
|
+
toTurnResult(response, request) {
|
|
295
|
+
if (!response) {
|
|
296
|
+
return { state: 'error', phase: 'capture', assistantMessage: 'Resposta vazia da IA.', errorText: 'Resposta vazia da IA.' };
|
|
297
|
+
}
|
|
298
|
+
if (response.type === 'clarification') {
|
|
299
|
+
return {
|
|
300
|
+
state: 'clarification',
|
|
301
|
+
phase: 'clarify',
|
|
302
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
303
|
+
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
304
|
+
clarificationQuestions: this.toClarificationQuestions(response),
|
|
305
|
+
quickReplies: this.toQuickReplies(response),
|
|
306
|
+
canApply: false,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (response.type === 'info') {
|
|
310
|
+
const message = response.message || response.explanation || 'Informacao gerada.';
|
|
311
|
+
return { state: 'success', phase: 'summarize', sessionId: response.sessionId ?? request.sessionId, assistantMessage: message, statusText: message, canApply: false };
|
|
312
|
+
}
|
|
313
|
+
if (response.type === 'error') {
|
|
314
|
+
const message = response.message || 'Falha ao gerar alteracao de expansion.';
|
|
315
|
+
return {
|
|
316
|
+
state: 'error',
|
|
317
|
+
phase: 'capture',
|
|
318
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
319
|
+
assistantMessage: message,
|
|
320
|
+
errorText: message,
|
|
321
|
+
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
325
|
+
return {
|
|
326
|
+
state: 'error',
|
|
327
|
+
phase: 'review',
|
|
328
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
329
|
+
assistantMessage: 'O expansion rejeitou patch livre. Gere um componentEditPlan validado pelo PRAXIS_EXPANSION_AUTHORING_MANIFEST antes de propor alteracao local.',
|
|
330
|
+
errorText: 'Patch livre de expansion rejeitado.',
|
|
331
|
+
canApply: false,
|
|
332
|
+
pendingPatch: null,
|
|
333
|
+
diagnostics: {
|
|
334
|
+
warnings: [
|
|
335
|
+
'free-expansion-patch-rejected',
|
|
336
|
+
'Use componentEditPlan validado contra PRAXIS_EXPANSION_AUTHORING_MANIFEST.',
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
state: 'success',
|
|
343
|
+
phase: 'summarize',
|
|
344
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
345
|
+
assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
346
|
+
statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
347
|
+
canApply: false,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
compileAdapterResponse(response) {
|
|
351
|
+
const compiled = this.adapter.compileAiResponse?.(response);
|
|
352
|
+
if (!compiled)
|
|
353
|
+
return response;
|
|
354
|
+
if (compiled.type === 'error') {
|
|
355
|
+
return {
|
|
356
|
+
type: 'error',
|
|
357
|
+
message: compiled.message || 'O componentEditPlan do expansion nao passou na validacao de capacidades.',
|
|
358
|
+
warnings: compiled.warnings,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const warnings = [...(response.warnings ?? []), ...(compiled.warnings ?? [])];
|
|
362
|
+
return { ...response, ...compiled, patch: compiled.patch, warnings: warnings.length ? warnings : undefined };
|
|
363
|
+
}
|
|
364
|
+
toChatMessages(messages, prompt) {
|
|
365
|
+
const supported = (messages ?? [])
|
|
366
|
+
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
367
|
+
.map((message) => ({ role: message.role, content: message.text }))
|
|
368
|
+
.filter((message) => message.content.trim().length > 0);
|
|
369
|
+
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
370
|
+
}
|
|
371
|
+
toClarificationQuestions(response) {
|
|
372
|
+
const labels = response.questions?.length
|
|
373
|
+
? response.questions
|
|
374
|
+
: response.message ? [response.message] : ['Qual ajuste voce quer aplicar nos paineis?'];
|
|
375
|
+
const options = this.toQuickReplies(response).map((reply) => ({ id: reply.id, label: reply.label, value: reply.prompt }));
|
|
376
|
+
return labels.map((label, index) => ({
|
|
377
|
+
id: `expansion-clarification-${index + 1}`,
|
|
378
|
+
type: options.length ? 'single-choice' : 'text',
|
|
379
|
+
label,
|
|
380
|
+
allowCustom: true,
|
|
381
|
+
options,
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
toQuickReplies(response) {
|
|
385
|
+
const payloads = response.optionPayloads ?? [];
|
|
386
|
+
if (payloads.length) {
|
|
387
|
+
return payloads.map((option, index) => {
|
|
388
|
+
const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
389
|
+
const prompt = option.example?.trim() || option.value?.trim() || label;
|
|
390
|
+
return { id: `option-${index + 1}`, label, prompt, kind: 'clarification-option' };
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return (response.options ?? [])
|
|
394
|
+
.filter((option) => !!option?.trim())
|
|
395
|
+
.map((option, index) => ({ id: `option-${index + 1}`, label: option.trim(), prompt: option.trim(), kind: 'clarification-option' }));
|
|
396
|
+
}
|
|
397
|
+
buildCurrentStateDigest(dataProfile) {
|
|
398
|
+
const panelCount = typeof dataProfile?.['panelCount'] === 'number' ? dataProfile['panelCount'] : undefined;
|
|
399
|
+
return panelCount !== undefined ? { rowCount: panelCount } : {};
|
|
400
|
+
}
|
|
401
|
+
shouldRouteToGovernedDecision(prompt, contextHints) {
|
|
402
|
+
return shouldRoutePromptToGovernedDecision(prompt, contextHints);
|
|
403
|
+
}
|
|
404
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
405
|
+
const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. O expansion pode localizar o painel e a experiencia afetada, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
|
|
406
|
+
return {
|
|
407
|
+
state: 'clarification',
|
|
408
|
+
phase: 'clarify',
|
|
409
|
+
sessionId: request.sessionId,
|
|
410
|
+
assistantMessage: message,
|
|
411
|
+
statusText: 'Handoff governado necessario.',
|
|
412
|
+
canApply: false,
|
|
413
|
+
quickReplies: [
|
|
414
|
+
{
|
|
415
|
+
id: 'shared-rule-handoff',
|
|
416
|
+
label: 'Continuar como regra governada',
|
|
417
|
+
prompt,
|
|
418
|
+
kind: 'shared-rule-handoff',
|
|
419
|
+
description: 'Criar intake de domain-rules em vez de aplicar patch local no expansion.',
|
|
420
|
+
icon: 'rule',
|
|
421
|
+
tone: 'warning',
|
|
422
|
+
contextHints: {
|
|
423
|
+
flowId: 'shared_rule_authoring',
|
|
424
|
+
source: 'praxis-expansion',
|
|
425
|
+
recommendedAction: 'domain-rules/intake',
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
clarificationQuestions: [
|
|
430
|
+
{
|
|
431
|
+
id: 'expansion-governed-rule-confirmation',
|
|
432
|
+
type: 'confirm',
|
|
433
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
434
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
435
|
+
required: true,
|
|
436
|
+
options: [
|
|
437
|
+
{
|
|
438
|
+
id: 'shared-rule-handoff',
|
|
439
|
+
label: 'Sim, continuar governado',
|
|
440
|
+
value: prompt,
|
|
441
|
+
description: 'Nao aplicar como patch local do expansion.',
|
|
442
|
+
contextHints: { flowId: 'shared_rule_authoring', source: 'praxis-expansion' },
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
diagnostics: {
|
|
448
|
+
governedDecisionHandoff: {
|
|
449
|
+
flowId: 'shared_rule_authoring',
|
|
450
|
+
sourcePrompt: prompt,
|
|
451
|
+
sourceComponent: 'praxis-expansion',
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
optionalJsonObject(value) {
|
|
457
|
+
if (value === undefined || value === null)
|
|
458
|
+
return undefined;
|
|
459
|
+
const object = this.toAiJsonObject(value);
|
|
460
|
+
return Object.keys(object).length ? object : undefined;
|
|
461
|
+
}
|
|
462
|
+
toAiJsonObject(value) {
|
|
463
|
+
const record = this.toRecord(value);
|
|
464
|
+
if (!record)
|
|
465
|
+
return {};
|
|
466
|
+
try {
|
|
467
|
+
return JSON.parse(JSON.stringify(record));
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
toRecord(value) {
|
|
474
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
475
|
+
? value
|
|
476
|
+
: null;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
187
480
|
class PraxisExpansion {
|
|
188
481
|
config;
|
|
189
482
|
expansionId;
|
|
@@ -211,9 +504,35 @@ class PraxisExpansion {
|
|
|
211
504
|
catch {
|
|
212
505
|
return undefined;
|
|
213
506
|
} })();
|
|
507
|
+
aiApi = inject(AiBackendApiService);
|
|
508
|
+
assistantSessions = inject(PraxisAssistantSessionRegistryService);
|
|
509
|
+
aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
|
|
510
|
+
aiAssistantSessionEffect = effect(() => {
|
|
511
|
+
const session = this.assistantSessions.activeSession();
|
|
512
|
+
if (!session || session.id !== this.resolveAiAssistantSessionId())
|
|
513
|
+
return;
|
|
514
|
+
if (!this.aiAssistantOpen) {
|
|
515
|
+
this.openAiAssistantFromSession(session);
|
|
516
|
+
}
|
|
517
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
|
|
214
518
|
warnedMissingId = false;
|
|
215
519
|
panelForms = new Map();
|
|
216
520
|
aiAdapter = new ExpansionAiAdapter(this);
|
|
521
|
+
aiAssistantOpen = false;
|
|
522
|
+
aiAssistantPrompt = '';
|
|
523
|
+
aiAssistantViewState = null;
|
|
524
|
+
aiAssistantLayout = createPraxisAssistantViewportLayout();
|
|
525
|
+
aiAssistantLabels = {
|
|
526
|
+
title: 'Copiloto semantico Praxis',
|
|
527
|
+
subtitle: 'Converse, revise e governe ajustes dos paineis.',
|
|
528
|
+
prompt: 'Mensagem',
|
|
529
|
+
promptPlaceholder: 'Descreva o ajuste que voce precisa nos paineis.',
|
|
530
|
+
emptyConversation: 'Diga o que voce quer alterar no expansion.',
|
|
531
|
+
submit: 'Interpretar pedido',
|
|
532
|
+
apply: 'Aplicar ajuste',
|
|
533
|
+
};
|
|
534
|
+
aiAssistantController = null;
|
|
535
|
+
aiAssistantStateSubscription = null;
|
|
217
536
|
accordionRef;
|
|
218
537
|
panels;
|
|
219
538
|
injectedDefaults = inject(MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, { optional: true });
|
|
@@ -237,6 +556,10 @@ class PraxisExpansion {
|
|
|
237
556
|
this.persistConfig(this.config);
|
|
238
557
|
}
|
|
239
558
|
}
|
|
559
|
+
ngOnDestroy() {
|
|
560
|
+
this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
|
|
561
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
562
|
+
}
|
|
240
563
|
styleCss() {
|
|
241
564
|
const t = this.config?.appearance?.tokens;
|
|
242
565
|
const appearance = this.config?.appearance;
|
|
@@ -476,6 +799,306 @@ class PraxisExpansion {
|
|
|
476
799
|
}
|
|
477
800
|
this.cdr.markForCheck();
|
|
478
801
|
}
|
|
802
|
+
openAiAssistant() {
|
|
803
|
+
this.initializeAiAssistantController();
|
|
804
|
+
this.aiAssistantOpen = true;
|
|
805
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
806
|
+
this.syncAiAssistantSession('active');
|
|
807
|
+
this.cdr.markForCheck();
|
|
808
|
+
}
|
|
809
|
+
openAiAssistantFromSession(session) {
|
|
810
|
+
if (session.id !== this.resolveAiAssistantSessionId())
|
|
811
|
+
return;
|
|
812
|
+
this.initializeAiAssistantController();
|
|
813
|
+
this.aiAssistantOpen = true;
|
|
814
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
815
|
+
this.syncAiAssistantSession('active');
|
|
816
|
+
this.cdr.markForCheck();
|
|
817
|
+
}
|
|
818
|
+
closeAiAssistant() {
|
|
819
|
+
this.aiAssistantOpen = false;
|
|
820
|
+
this.syncAiAssistantSession('minimized');
|
|
821
|
+
this.cdr.markForCheck();
|
|
822
|
+
}
|
|
823
|
+
onAiAssistantPromptChange(prompt) {
|
|
824
|
+
this.aiAssistantPrompt = prompt;
|
|
825
|
+
this.syncAiAssistantSession();
|
|
826
|
+
}
|
|
827
|
+
onAiAssistantSubmit(prompt) {
|
|
828
|
+
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
829
|
+
this.aiAssistantPrompt = '';
|
|
830
|
+
this.aiAssistantViewState = state;
|
|
831
|
+
this.syncAiAssistantSession();
|
|
832
|
+
this.cdr.markForCheck();
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
onAiAssistantApply() {
|
|
836
|
+
this.aiAssistantController?.apply().subscribe((state) => {
|
|
837
|
+
this.aiAssistantViewState = state;
|
|
838
|
+
this.syncAiAssistantSession();
|
|
839
|
+
this.cdr.markForCheck();
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
onAiAssistantRetry() {
|
|
843
|
+
this.aiAssistantController?.retry().subscribe((state) => {
|
|
844
|
+
this.aiAssistantViewState = state;
|
|
845
|
+
this.syncAiAssistantSession();
|
|
846
|
+
this.cdr.markForCheck();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
onAiAssistantCancel() {
|
|
850
|
+
this.aiAssistantController?.cancel().subscribe((state) => {
|
|
851
|
+
this.aiAssistantPrompt = '';
|
|
852
|
+
this.aiAssistantViewState = state;
|
|
853
|
+
this.syncAiAssistantSession();
|
|
854
|
+
this.cdr.markForCheck();
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
onAiAssistantQuickReply(reply) {
|
|
858
|
+
const controller = this.aiAssistantController;
|
|
859
|
+
if (!controller)
|
|
860
|
+
return;
|
|
861
|
+
const state = controller.snapshot();
|
|
862
|
+
const next$ = state.state === 'clarification'
|
|
863
|
+
? controller.answerClarification(reply.prompt)
|
|
864
|
+
: controller.submitPrompt(reply.prompt, {
|
|
865
|
+
kind: reply.kind || 'quick-reply',
|
|
866
|
+
id: reply.id,
|
|
867
|
+
value: reply.prompt,
|
|
868
|
+
});
|
|
869
|
+
next$.subscribe((nextState) => {
|
|
870
|
+
this.aiAssistantPrompt = '';
|
|
871
|
+
this.aiAssistantViewState = nextState;
|
|
872
|
+
this.syncAiAssistantSession();
|
|
873
|
+
this.cdr.markForCheck();
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
onAiAssistantEditMessage(message) {
|
|
877
|
+
this.aiAssistantPrompt = message.text;
|
|
878
|
+
this.cdr.markForCheck();
|
|
879
|
+
}
|
|
880
|
+
onAiAssistantResendMessage(message) {
|
|
881
|
+
this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
|
|
882
|
+
this.aiAssistantPrompt = '';
|
|
883
|
+
this.aiAssistantViewState = state;
|
|
884
|
+
this.syncAiAssistantSession();
|
|
885
|
+
this.cdr.markForCheck();
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
onAiAssistantLayoutChange(layout) {
|
|
889
|
+
this.aiAssistantLayout = layout;
|
|
890
|
+
}
|
|
891
|
+
buildAiAssistantContextItems() {
|
|
892
|
+
const panels = this.config?.panels ?? [];
|
|
893
|
+
const items = [
|
|
894
|
+
{
|
|
895
|
+
id: 'component',
|
|
896
|
+
label: 'Componente',
|
|
897
|
+
value: 'Expansion',
|
|
898
|
+
kind: 'component',
|
|
899
|
+
icon: 'unfold_more',
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
id: 'expansion-id',
|
|
903
|
+
label: 'Expansion',
|
|
904
|
+
value: this.safeAiAssistantExpansionId(),
|
|
905
|
+
kind: 'custom',
|
|
906
|
+
icon: 'tag',
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
id: 'panels',
|
|
910
|
+
label: 'Paineis',
|
|
911
|
+
value: String(panels.length),
|
|
912
|
+
kind: 'custom',
|
|
913
|
+
icon: 'view_agenda',
|
|
914
|
+
},
|
|
915
|
+
];
|
|
916
|
+
const expanded = panels.filter((panel) => panel.expanded).length;
|
|
917
|
+
if (expanded > 0) {
|
|
918
|
+
items.push({
|
|
919
|
+
id: 'expanded-panels',
|
|
920
|
+
label: 'Expandidos',
|
|
921
|
+
value: String(expanded),
|
|
922
|
+
kind: 'custom',
|
|
923
|
+
icon: 'expand_less',
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
return items;
|
|
927
|
+
}
|
|
928
|
+
initializeAiAssistantController() {
|
|
929
|
+
if (this.aiAssistantController)
|
|
930
|
+
return;
|
|
931
|
+
const flow = new ExpansionAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
|
|
932
|
+
const controller = this.aiTurnOrchestrator.createController(flow, {
|
|
933
|
+
componentId: this.aiAdapter.componentId || 'praxis-expansion',
|
|
934
|
+
componentType: this.aiAdapter.componentType || 'expansion',
|
|
935
|
+
contextItems: this.buildAiAssistantContextItems(),
|
|
936
|
+
});
|
|
937
|
+
this.aiAssistantController = controller;
|
|
938
|
+
this.aiAssistantViewState = controller.snapshot();
|
|
939
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
940
|
+
this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
|
|
941
|
+
this.aiAssistantViewState = state;
|
|
942
|
+
this.syncAiAssistantSession();
|
|
943
|
+
this.cdr.markForCheck();
|
|
944
|
+
});
|
|
945
|
+
this.cdr.markForCheck();
|
|
946
|
+
}
|
|
947
|
+
buildAiAssistantContextSnapshot() {
|
|
948
|
+
const counts = this.collectAiAssistantCounts();
|
|
949
|
+
const panelNames = this.collectAiAssistantPanelNames();
|
|
950
|
+
return {
|
|
951
|
+
identity: {
|
|
952
|
+
sessionId: this.resolveAiAssistantSessionId(),
|
|
953
|
+
ownerId: this.resolveAiAssistantOwnerId(),
|
|
954
|
+
ownerType: 'expansion',
|
|
955
|
+
componentId: 'praxis-expansion',
|
|
956
|
+
componentType: 'expansion',
|
|
957
|
+
routeKey: this.resolveAiAssistantRouteKey(),
|
|
958
|
+
},
|
|
959
|
+
target: {
|
|
960
|
+
kind: 'component',
|
|
961
|
+
id: this.resolveAiAssistantOwnerId(),
|
|
962
|
+
label: this.safeAiAssistantExpansionId(),
|
|
963
|
+
metadata: {
|
|
964
|
+
expansionId: this.safeAiAssistantExpansionId(),
|
|
965
|
+
hasCustomization: !!this.enableCustomization,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
contextItems: this.buildAiAssistantContextItems().map((item) => ({
|
|
969
|
+
id: item.id,
|
|
970
|
+
label: item.label,
|
|
971
|
+
value: item.value || '',
|
|
972
|
+
kind: item.kind,
|
|
973
|
+
})),
|
|
974
|
+
mode: 'agentic-authoring',
|
|
975
|
+
authoringManifestRef: {
|
|
976
|
+
componentId: 'praxis-expansion',
|
|
977
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
978
|
+
},
|
|
979
|
+
schemaFields: panelNames.length ? panelNames : undefined,
|
|
980
|
+
dataProfileDigest: {
|
|
981
|
+
summary: `${counts.panelCount} painel(is), ${counts.contentPanelCount} com campo(s), ${counts.widgetPanelCount} com widget(s)`,
|
|
982
|
+
counts,
|
|
983
|
+
},
|
|
984
|
+
runtimeStateDigest: {
|
|
985
|
+
summary: `Expansion ${this.config?.accordion?.multi ? 'multi' : 'single'}, ${counts.expandedCount} painel(is) expandido(s)`,
|
|
986
|
+
fields: [
|
|
987
|
+
'panels',
|
|
988
|
+
'accordion',
|
|
989
|
+
'content',
|
|
990
|
+
'widgets',
|
|
991
|
+
],
|
|
992
|
+
},
|
|
993
|
+
capabilityRefs: [
|
|
994
|
+
{
|
|
995
|
+
id: 'expansion.component-edit-plan',
|
|
996
|
+
label: 'Plano de edicao de paineis',
|
|
997
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
998
|
+
risk: 'medium',
|
|
999
|
+
},
|
|
1000
|
+
],
|
|
1001
|
+
governanceHints: [
|
|
1002
|
+
{
|
|
1003
|
+
kind: 'business-rule-boundary',
|
|
1004
|
+
label: 'Regras compartilhadas exigem governanca',
|
|
1005
|
+
reason: 'Politicas de acesso, validacoes reutilizaveis e compliance nao devem ser aplicadas como patch local do expansion.',
|
|
1006
|
+
risk: 'high',
|
|
1007
|
+
},
|
|
1008
|
+
],
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
syncAiAssistantSession(visibility = null) {
|
|
1012
|
+
if (!this.enableCustomization)
|
|
1013
|
+
return;
|
|
1014
|
+
if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
|
|
1015
|
+
return;
|
|
1016
|
+
const state = this.aiAssistantViewState;
|
|
1017
|
+
this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
|
|
1018
|
+
title: 'Copiloto semantico Praxis',
|
|
1019
|
+
summary: this.resolveAiAssistantSummary(),
|
|
1020
|
+
mode: state?.mode || 'agentic-authoring',
|
|
1021
|
+
state: state?.state || 'idle',
|
|
1022
|
+
visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
|
|
1023
|
+
badge: this.resolveAiAssistantBadge(),
|
|
1024
|
+
icon: this.resolveAiAssistantIcon(),
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
hasAiAssistantSessionState() {
|
|
1028
|
+
return !!this.aiAssistantPrompt.trim()
|
|
1029
|
+
|| !!this.aiAssistantViewState?.messages?.length
|
|
1030
|
+
|| !!this.aiAssistantViewState?.quickReplies?.length
|
|
1031
|
+
|| !!this.aiAssistantViewState?.pendingPatch
|
|
1032
|
+
|| !!this.aiAssistantViewState?.statusText?.trim()
|
|
1033
|
+
|| !!this.aiAssistantViewState?.errorText?.trim();
|
|
1034
|
+
}
|
|
1035
|
+
resolveAiAssistantSessionId() {
|
|
1036
|
+
return `expansion:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
|
|
1037
|
+
}
|
|
1038
|
+
resolveAiAssistantOwnerId() {
|
|
1039
|
+
return (this.componentInstanceId || this.safeAiAssistantExpansionId() || 'expansion').trim() || 'expansion';
|
|
1040
|
+
}
|
|
1041
|
+
safeAiAssistantExpansionId() {
|
|
1042
|
+
return String(this.expansionId || '').trim();
|
|
1043
|
+
}
|
|
1044
|
+
resolveAiAssistantRouteKey() {
|
|
1045
|
+
const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
|
|
1046
|
+
return routePath || 'local';
|
|
1047
|
+
}
|
|
1048
|
+
resolveAiAssistantSummary() {
|
|
1049
|
+
const status = this.aiAssistantViewState?.statusText?.trim();
|
|
1050
|
+
if (status)
|
|
1051
|
+
return status;
|
|
1052
|
+
const error = this.aiAssistantViewState?.errorText?.trim();
|
|
1053
|
+
if (error)
|
|
1054
|
+
return error;
|
|
1055
|
+
const prompt = this.aiAssistantPrompt.trim();
|
|
1056
|
+
if (prompt)
|
|
1057
|
+
return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
|
|
1058
|
+
const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
|
|
1059
|
+
.find((message) => message.role === 'assistant' || message.role === 'user');
|
|
1060
|
+
if (lastMessage?.text) {
|
|
1061
|
+
return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
|
|
1062
|
+
}
|
|
1063
|
+
return 'Assistente contextual dos paineis.';
|
|
1064
|
+
}
|
|
1065
|
+
resolveAiAssistantBadge() {
|
|
1066
|
+
const state = this.aiAssistantViewState?.state;
|
|
1067
|
+
if (state === 'error')
|
|
1068
|
+
return 'erro';
|
|
1069
|
+
if (state === 'clarification')
|
|
1070
|
+
return 'revisar';
|
|
1071
|
+
if (state === 'review')
|
|
1072
|
+
return 'preview';
|
|
1073
|
+
if (state === 'success')
|
|
1074
|
+
return 'ok';
|
|
1075
|
+
return undefined;
|
|
1076
|
+
}
|
|
1077
|
+
resolveAiAssistantIcon() {
|
|
1078
|
+
const state = this.aiAssistantViewState?.state;
|
|
1079
|
+
if (state === 'error')
|
|
1080
|
+
return 'error';
|
|
1081
|
+
if (state === 'clarification')
|
|
1082
|
+
return 'rule';
|
|
1083
|
+
if (state === 'review')
|
|
1084
|
+
return 'rate_review';
|
|
1085
|
+
return 'auto_awesome';
|
|
1086
|
+
}
|
|
1087
|
+
collectAiAssistantPanelNames() {
|
|
1088
|
+
return Array.from(new Set((this.config?.panels ?? [])
|
|
1089
|
+
.map((panel, index) => panel.id || panel.title || `panel-${index + 1}`)
|
|
1090
|
+
.filter((name) => typeof name === 'string' && !!name.trim())));
|
|
1091
|
+
}
|
|
1092
|
+
collectAiAssistantCounts() {
|
|
1093
|
+
const panels = this.config?.panels ?? [];
|
|
1094
|
+
return {
|
|
1095
|
+
panelCount: panels.length,
|
|
1096
|
+
contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
|
|
1097
|
+
widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
|
|
1098
|
+
actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
|
|
1099
|
+
expandedCount: panels.filter((panel) => !!panel.expanded).length,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
479
1102
|
openEditor() {
|
|
480
1103
|
const key = this.storageKey() || this.expansionId || 'default';
|
|
481
1104
|
const ref = this.settings.open({
|
|
@@ -573,10 +1196,48 @@ class PraxisExpansion {
|
|
|
573
1196
|
|
|
574
1197
|
@if (enableCustomization) {
|
|
575
1198
|
<div class="expansion-ai-assistant">
|
|
576
|
-
<
|
|
1199
|
+
<button
|
|
1200
|
+
mat-mini-fab
|
|
1201
|
+
color="primary"
|
|
1202
|
+
type="button"
|
|
1203
|
+
class="expansion-ai-assistant-trigger"
|
|
1204
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1205
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1206
|
+
(click)="openAiAssistant()"
|
|
1207
|
+
>
|
|
1208
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1209
|
+
</button>
|
|
577
1210
|
</div>
|
|
578
1211
|
}
|
|
579
1212
|
|
|
1213
|
+
<praxis-ai-assistant-shell
|
|
1214
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
1215
|
+
[labels]="aiAssistantLabels"
|
|
1216
|
+
[mode]="aiAssistantViewState.mode"
|
|
1217
|
+
[state]="aiAssistantViewState.state"
|
|
1218
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1219
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1220
|
+
[messages]="aiAssistantViewState.messages"
|
|
1221
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1222
|
+
[prompt]="aiAssistantPrompt"
|
|
1223
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1224
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1225
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1226
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1227
|
+
[layout]="aiAssistantLayout"
|
|
1228
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1229
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1230
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1231
|
+
(apply)="onAiAssistantApply()"
|
|
1232
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1233
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1234
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1235
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1236
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1237
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1238
|
+
(close)="closeAiAssistant()"
|
|
1239
|
+
></praxis-ai-assistant-shell>
|
|
1240
|
+
|
|
580
1241
|
@if (hasMultiple()) {
|
|
581
1242
|
<mat-accordion
|
|
582
1243
|
#accordion
|
|
@@ -603,8 +1264,9 @@ class PraxisExpansion {
|
|
|
603
1264
|
>
|
|
604
1265
|
<mat-expansion-panel-header
|
|
605
1266
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
606
|
-
|
|
607
|
-
|
|
1267
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1268
|
+
>
|
|
1269
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
608
1270
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
609
1271
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
610
1272
|
</mat-expansion-panel-header>
|
|
@@ -660,8 +1322,9 @@ class PraxisExpansion {
|
|
|
660
1322
|
>
|
|
661
1323
|
<mat-expansion-panel-header
|
|
662
1324
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
663
|
-
|
|
664
|
-
|
|
1325
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1326
|
+
>
|
|
1327
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
665
1328
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
666
1329
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
667
1330
|
</mat-expansion-panel-header>
|
|
@@ -718,7 +1381,7 @@ class PraxisExpansion {
|
|
|
718
1381
|
>
|
|
719
1382
|
<mat-icon fontIcon="edit"></mat-icon>
|
|
720
1383
|
</button>
|
|
721
|
-
`, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { 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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type:
|
|
1384
|
+
`, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { 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 });
|
|
722
1385
|
}
|
|
723
1386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansion, decorators: [{
|
|
724
1387
|
type: Component,
|
|
@@ -730,7 +1393,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
730
1393
|
DynamicFieldLoaderDirective,
|
|
731
1394
|
DynamicWidgetLoaderDirective,
|
|
732
1395
|
PraxisIconDirective,
|
|
733
|
-
|
|
1396
|
+
PraxisAiAssistantShellComponent,
|
|
734
1397
|
], template: `
|
|
735
1398
|
<div
|
|
736
1399
|
class="praxis-expansion-root"
|
|
@@ -745,10 +1408,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
745
1408
|
|
|
746
1409
|
@if (enableCustomization) {
|
|
747
1410
|
<div class="expansion-ai-assistant">
|
|
748
|
-
<
|
|
1411
|
+
<button
|
|
1412
|
+
mat-mini-fab
|
|
1413
|
+
color="primary"
|
|
1414
|
+
type="button"
|
|
1415
|
+
class="expansion-ai-assistant-trigger"
|
|
1416
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1417
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1418
|
+
(click)="openAiAssistant()"
|
|
1419
|
+
>
|
|
1420
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1421
|
+
</button>
|
|
749
1422
|
</div>
|
|
750
1423
|
}
|
|
751
1424
|
|
|
1425
|
+
<praxis-ai-assistant-shell
|
|
1426
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
1427
|
+
[labels]="aiAssistantLabels"
|
|
1428
|
+
[mode]="aiAssistantViewState.mode"
|
|
1429
|
+
[state]="aiAssistantViewState.state"
|
|
1430
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1431
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1432
|
+
[messages]="aiAssistantViewState.messages"
|
|
1433
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1434
|
+
[prompt]="aiAssistantPrompt"
|
|
1435
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1436
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1437
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1438
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1439
|
+
[layout]="aiAssistantLayout"
|
|
1440
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1441
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1442
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1443
|
+
(apply)="onAiAssistantApply()"
|
|
1444
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1445
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1446
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1447
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1448
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1449
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1450
|
+
(close)="closeAiAssistant()"
|
|
1451
|
+
></praxis-ai-assistant-shell>
|
|
1452
|
+
|
|
752
1453
|
@if (hasMultiple()) {
|
|
753
1454
|
<mat-accordion
|
|
754
1455
|
#accordion
|
|
@@ -775,8 +1476,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
775
1476
|
>
|
|
776
1477
|
<mat-expansion-panel-header
|
|
777
1478
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
778
|
-
|
|
779
|
-
|
|
1479
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1480
|
+
>
|
|
1481
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
780
1482
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
781
1483
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
782
1484
|
</mat-expansion-panel-header>
|
|
@@ -832,8 +1534,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
832
1534
|
>
|
|
833
1535
|
<mat-expansion-panel-header
|
|
834
1536
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
835
|
-
|
|
836
|
-
|
|
1537
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1538
|
+
>
|
|
1539
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
837
1540
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
838
1541
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
839
1542
|
</mat-expansion-panel-header>
|
|
@@ -890,7 +1593,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
890
1593
|
>
|
|
891
1594
|
<mat-icon fontIcon="edit"></mat-icon>
|
|
892
1595
|
</button>
|
|
893
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\n"] }]
|
|
1596
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"] }]
|
|
894
1597
|
}], propDecorators: { config: [{
|
|
895
1598
|
type: Input
|
|
896
1599
|
}], expansionId: [{
|
|
@@ -1099,7 +1802,7 @@ class PraxisExpansionConfigEditor {
|
|
|
1099
1802
|
}
|
|
1100
1803
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
|
|
1101
1804
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisExpansionConfigEditor, isStandalone: true, selector: "praxis-expansion-config-editor", inputs: { config: "config", expansionId: "expansionId" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1102
|
-
<div class="pdx-expansion-editor">
|
|
1805
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1103
1806
|
<section class="sec">
|
|
1104
1807
|
<h4>Aparência</h4>
|
|
1105
1808
|
<div class="grid two">
|
|
@@ -1321,6 +2024,9 @@ class PraxisExpansionConfigEditor {
|
|
|
1321
2024
|
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
1322
2025
|
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
1323
2026
|
</mat-form-field>
|
|
2027
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2028
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2029
|
+
</mat-form-field>
|
|
1324
2030
|
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
1325
2031
|
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
1326
2032
|
</mat-form-field>
|
|
@@ -1349,7 +2055,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1349
2055
|
MatTooltipModule,
|
|
1350
2056
|
PraxisIconDirective,
|
|
1351
2057
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1352
|
-
<div class="pdx-expansion-editor">
|
|
2058
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1353
2059
|
<section class="sec">
|
|
1354
2060
|
<h4>Aparência</h4>
|
|
1355
2061
|
<div class="grid two">
|
|
@@ -1571,6 +2277,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1571
2277
|
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
1572
2278
|
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
1573
2279
|
</mat-form-field>
|
|
2280
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2281
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2282
|
+
</mat-form-field>
|
|
1574
2283
|
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
1575
2284
|
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
1576
2285
|
</mat-form-field>
|
|
@@ -1595,6 +2304,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1595
2304
|
type: Input
|
|
1596
2305
|
}] } });
|
|
1597
2306
|
|
|
2307
|
+
class PraxisExpansionWidgetConfigEditor {
|
|
2308
|
+
inputs = null;
|
|
2309
|
+
widgetKey;
|
|
2310
|
+
expansionEditor;
|
|
2311
|
+
isDirty$ = new BehaviorSubject(false);
|
|
2312
|
+
isValid$ = new BehaviorSubject(true);
|
|
2313
|
+
isBusy$ = new BehaviorSubject(false);
|
|
2314
|
+
subscription = new Subscription();
|
|
2315
|
+
emptyConfig = {};
|
|
2316
|
+
get config() {
|
|
2317
|
+
return this.inputs?.config ?? this.emptyConfig;
|
|
2318
|
+
}
|
|
2319
|
+
get expansionId() {
|
|
2320
|
+
return this.inputs?.expansionId ?? this.widgetKey;
|
|
2321
|
+
}
|
|
2322
|
+
ngAfterViewInit() {
|
|
2323
|
+
if (!this.expansionEditor) {
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
this.subscription.add(this.expansionEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
|
|
2327
|
+
this.subscription.add(this.expansionEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
|
|
2328
|
+
this.subscription.add(this.expansionEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
|
|
2329
|
+
}
|
|
2330
|
+
ngOnDestroy() {
|
|
2331
|
+
this.subscription.unsubscribe();
|
|
2332
|
+
}
|
|
2333
|
+
getSettingsValue() {
|
|
2334
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2335
|
+
}
|
|
2336
|
+
onSave() {
|
|
2337
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2338
|
+
}
|
|
2339
|
+
reset() {
|
|
2340
|
+
this.expansionEditor?.reset();
|
|
2341
|
+
}
|
|
2342
|
+
buildValue(config) {
|
|
2343
|
+
return {
|
|
2344
|
+
inputs: {
|
|
2345
|
+
...(this.inputs ?? {}),
|
|
2346
|
+
config: config ?? this.config,
|
|
2347
|
+
...(this.expansionId ? { expansionId: this.expansionId } : {}),
|
|
2348
|
+
},
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2352
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisExpansionWidgetConfigEditor, isStandalone: true, selector: "praxis-expansion-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "expansionEditor", first: true, predicate: ["expansionEditor"], descendants: true }], ngImport: i0, template: `
|
|
2353
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2354
|
+
<praxis-expansion-config-editor
|
|
2355
|
+
#expansionEditor
|
|
2356
|
+
[config]="config"
|
|
2357
|
+
[expansionId]="expansionId"
|
|
2358
|
+
/>
|
|
2359
|
+
</section>
|
|
2360
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisExpansionConfigEditor, selector: "praxis-expansion-config-editor", inputs: ["config", "expansionId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2361
|
+
}
|
|
2362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, decorators: [{
|
|
2363
|
+
type: Component,
|
|
2364
|
+
args: [{
|
|
2365
|
+
selector: 'praxis-expansion-widget-config-editor',
|
|
2366
|
+
standalone: true,
|
|
2367
|
+
imports: [CommonModule, PraxisExpansionConfigEditor],
|
|
2368
|
+
template: `
|
|
2369
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2370
|
+
<praxis-expansion-config-editor
|
|
2371
|
+
#expansionEditor
|
|
2372
|
+
[config]="config"
|
|
2373
|
+
[expansionId]="expansionId"
|
|
2374
|
+
/>
|
|
2375
|
+
</section>
|
|
2376
|
+
`,
|
|
2377
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2378
|
+
}]
|
|
2379
|
+
}], propDecorators: { inputs: [{
|
|
2380
|
+
type: Input
|
|
2381
|
+
}], widgetKey: [{
|
|
2382
|
+
type: Input
|
|
2383
|
+
}], expansionEditor: [{
|
|
2384
|
+
type: ViewChild,
|
|
2385
|
+
args: ['expansionEditor']
|
|
2386
|
+
}] } });
|
|
2387
|
+
|
|
2388
|
+
const PRAXIS_EXPANSION_PORTS = [
|
|
2389
|
+
{
|
|
2390
|
+
id: 'config',
|
|
2391
|
+
label: 'Configuracao',
|
|
2392
|
+
direction: 'input',
|
|
2393
|
+
semanticKind: 'config-fragment',
|
|
2394
|
+
schema: {
|
|
2395
|
+
id: 'ExpansionMetadata',
|
|
2396
|
+
kind: 'ts-type',
|
|
2397
|
+
ref: 'ExpansionMetadata',
|
|
2398
|
+
},
|
|
2399
|
+
description: 'Fragmento canonico de configuracao dos paineis e dos widgets internos.',
|
|
2400
|
+
exposure: { public: true, group: 'config' },
|
|
2401
|
+
},
|
|
2402
|
+
{
|
|
2403
|
+
id: 'widgetEvent',
|
|
2404
|
+
label: 'Evento interno de widget',
|
|
2405
|
+
direction: 'output',
|
|
2406
|
+
semanticKind: 'event',
|
|
2407
|
+
schema: {
|
|
2408
|
+
id: 'WidgetEventEnvelope',
|
|
2409
|
+
kind: 'ts-type',
|
|
2410
|
+
ref: 'WidgetEventEnvelope',
|
|
2411
|
+
},
|
|
2412
|
+
cardinality: 'stream',
|
|
2413
|
+
description: 'Bridge composta para transporte de eventos internos. As portas canonicas dos filhos sao resolvidas por component-port + nestedPath.',
|
|
2414
|
+
exposure: { public: true, advanced: true, group: 'composite' },
|
|
2415
|
+
},
|
|
2416
|
+
];
|
|
1598
2417
|
const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
1599
2418
|
id: 'praxis-expansion',
|
|
1600
2419
|
selector: 'praxis-expansion',
|
|
@@ -1602,6 +2421,14 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1602
2421
|
friendlyName: 'Praxis Expansion Panel',
|
|
1603
2422
|
description: 'Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.',
|
|
1604
2423
|
icon: 'unfold_more',
|
|
2424
|
+
authoringManifestRef: {
|
|
2425
|
+
componentId: 'praxis-expansion',
|
|
2426
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
2427
|
+
},
|
|
2428
|
+
configEditor: {
|
|
2429
|
+
component: PraxisExpansionWidgetConfigEditor,
|
|
2430
|
+
title: 'Configure expansion',
|
|
2431
|
+
},
|
|
1605
2432
|
inputs: [
|
|
1606
2433
|
{ name: 'config', type: 'ExpansionMetadata', label: 'Configuração', description: 'Configuração JSON (acordeão, aparência e painéis)' },
|
|
1607
2434
|
{ name: 'expansionId', type: 'string', label: 'ID', description: 'Identificador para persistência (obrigatório)' },
|
|
@@ -1676,6 +2503,7 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1676
2503
|
],
|
|
1677
2504
|
tags: ['widget', 'expansion', 'accordion', 'configurable'],
|
|
1678
2505
|
lib: '@praxisui/expansion',
|
|
2506
|
+
ports: PRAXIS_EXPANSION_PORTS,
|
|
1679
2507
|
};
|
|
1680
2508
|
function providePraxisExpansionMetadata() {
|
|
1681
2509
|
return {
|
|
@@ -1691,6 +2519,315 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1691
2519
|
return { provide: MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, useValue: opts };
|
|
1692
2520
|
}
|
|
1693
2521
|
|
|
2522
|
+
const panelItemSchema = {
|
|
2523
|
+
type: 'object',
|
|
2524
|
+
required: ['id', 'title'],
|
|
2525
|
+
properties: {
|
|
2526
|
+
id: { type: 'string' },
|
|
2527
|
+
title: { type: 'string' },
|
|
2528
|
+
description: { type: 'string' },
|
|
2529
|
+
icon: { type: 'string' },
|
|
2530
|
+
disabled: { type: 'boolean' },
|
|
2531
|
+
expanded: { type: 'boolean' },
|
|
2532
|
+
hideToggle: { type: 'boolean' },
|
|
2533
|
+
collapsedHeight: { type: 'string' },
|
|
2534
|
+
expandedHeight: { type: 'string' },
|
|
2535
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2536
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2537
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2538
|
+
},
|
|
2539
|
+
};
|
|
2540
|
+
const panelPatchSchema = {
|
|
2541
|
+
type: 'object',
|
|
2542
|
+
minProperties: 1,
|
|
2543
|
+
properties: {
|
|
2544
|
+
id: { type: 'string' },
|
|
2545
|
+
title: { type: 'string' },
|
|
2546
|
+
description: { type: 'string' },
|
|
2547
|
+
icon: { type: 'string' },
|
|
2548
|
+
disabled: { type: 'boolean' },
|
|
2549
|
+
expanded: { type: 'boolean' },
|
|
2550
|
+
hideToggle: { type: 'boolean' },
|
|
2551
|
+
collapsedHeight: { type: 'string' },
|
|
2552
|
+
expandedHeight: { type: 'string' },
|
|
2553
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2554
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2555
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2556
|
+
},
|
|
2557
|
+
};
|
|
2558
|
+
const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
|
|
2559
|
+
schemaVersion: '1.0.0',
|
|
2560
|
+
componentId: 'praxis-expansion',
|
|
2561
|
+
ownerPackage: '@praxisui/expansion',
|
|
2562
|
+
configSchemaId: 'ExpansionMetadata',
|
|
2563
|
+
manifestVersion: '1.0.0',
|
|
2564
|
+
runtimeInputs: [
|
|
2565
|
+
{ name: 'config', type: 'ExpansionMetadata', description: 'Canonical accordion and panel configuration.' },
|
|
2566
|
+
{ name: 'expansionId', type: 'string', description: 'Stable id used to derive expansion config persistence scope.' },
|
|
2567
|
+
{ name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
|
|
2568
|
+
{ name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
|
|
2569
|
+
{ name: 'strictValidation', type: 'boolean', description: 'Controls nested widget validation strictness.' },
|
|
2570
|
+
{ name: 'defaultOptions', type: 'MatExpansionPanelDefaultOptions', description: 'Instance-level Material expansion defaults.' },
|
|
2571
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
|
|
2572
|
+
],
|
|
2573
|
+
editableTargets: [
|
|
2574
|
+
{ kind: 'panel', resolver: 'panel-by-id-or-title', description: 'A panel in config.panels[].' },
|
|
2575
|
+
{ kind: 'panelHeader', resolver: 'panel-by-id-or-title', description: 'Header title, description, icon and heights for a panel.' },
|
|
2576
|
+
{ kind: 'panelContent', resolver: 'panel-content-by-id', description: 'Lazy panel content hosted through fields, widgets or action buttons.' },
|
|
2577
|
+
{ kind: 'expandedState', resolver: 'panel-by-id-or-title', description: 'Panel expanded state and default expanded selection.' },
|
|
2578
|
+
{ kind: 'disabledState', resolver: 'panel-by-id-or-title', description: 'Panel disabled state.' },
|
|
2579
|
+
{ kind: 'layout', resolver: 'expansion-layout-config', description: 'Accordion display mode, toggle position, density and visual layout.' },
|
|
2580
|
+
{ kind: 'behavior', resolver: 'expansion-behavior-config', description: 'Accordion multi-expand and hide-toggle behavior.' },
|
|
2581
|
+
],
|
|
2582
|
+
operations: [
|
|
2583
|
+
{
|
|
2584
|
+
operationId: 'panel.add',
|
|
2585
|
+
title: 'Add panel',
|
|
2586
|
+
scope: 'global',
|
|
2587
|
+
targetKind: 'panel',
|
|
2588
|
+
target: { kind: 'panel', resolver: 'panels-array', ambiguityPolicy: 'fail', required: false },
|
|
2589
|
+
inputSchema: panelItemSchema,
|
|
2590
|
+
effects: [{ kind: 'append-unique', path: 'panels[]', key: 'id' }],
|
|
2591
|
+
destructive: false,
|
|
2592
|
+
requiresConfirmation: false,
|
|
2593
|
+
validators: ['panel-id-unique', 'panel-order-deterministic', 'panel-content-valid'],
|
|
2594
|
+
affectedPaths: ['panels[]'],
|
|
2595
|
+
submissionImpact: 'config-only',
|
|
2596
|
+
preconditions: ['config-initialized'],
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
operationId: 'panel.remove',
|
|
2600
|
+
title: 'Remove panel',
|
|
2601
|
+
scope: 'layout',
|
|
2602
|
+
targetKind: 'panel',
|
|
2603
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2604
|
+
inputSchema: {
|
|
2605
|
+
type: 'object',
|
|
2606
|
+
properties: {
|
|
2607
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2608
|
+
},
|
|
2609
|
+
},
|
|
2610
|
+
effects: [{
|
|
2611
|
+
kind: 'compile-domain-patch',
|
|
2612
|
+
handler: 'expansion-panel-remove',
|
|
2613
|
+
handlerContract: {
|
|
2614
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2615
|
+
writes: ['panels[]', 'panels[].expanded'],
|
|
2616
|
+
identityKeys: ['panels[].id'],
|
|
2617
|
+
inputSchema: {
|
|
2618
|
+
type: 'object',
|
|
2619
|
+
properties: {
|
|
2620
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2621
|
+
},
|
|
2622
|
+
},
|
|
2623
|
+
failureModes: ['panel-not-found', 'replacement-panel-not-found', 'content-removal-not-confirmed', 'single-expand-conflict'],
|
|
2624
|
+
description: 'Removes the target panel by stable id and applies replacement expanded state when the removed panel was expanded.',
|
|
2625
|
+
},
|
|
2626
|
+
}],
|
|
2627
|
+
destructive: true,
|
|
2628
|
+
requiresConfirmation: true,
|
|
2629
|
+
validators: ['panel-exists', 'default-expanded-removal-safe', 'panel-content-removal-confirmed'],
|
|
2630
|
+
affectedPaths: ['panels[]', 'panels[].expanded'],
|
|
2631
|
+
submissionImpact: 'config-only',
|
|
2632
|
+
preconditions: ['config-initialized', 'target-panel-exists', 'confirmation-collected'],
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
operationId: 'panel.title.set',
|
|
2636
|
+
title: 'Set panel title',
|
|
2637
|
+
scope: 'layout',
|
|
2638
|
+
targetKind: 'panelHeader',
|
|
2639
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2640
|
+
inputSchema: { type: 'object', required: ['title'], properties: { title: { type: 'string' } } },
|
|
2641
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2642
|
+
destructive: false,
|
|
2643
|
+
requiresConfirmation: false,
|
|
2644
|
+
validators: ['panel-exists', 'panel-title-valid'],
|
|
2645
|
+
affectedPaths: ['panels[].title'],
|
|
2646
|
+
submissionImpact: 'config-only',
|
|
2647
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2648
|
+
},
|
|
2649
|
+
{
|
|
2650
|
+
operationId: 'panel.description.set',
|
|
2651
|
+
title: 'Set panel description',
|
|
2652
|
+
scope: 'layout',
|
|
2653
|
+
targetKind: 'panelHeader',
|
|
2654
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2655
|
+
inputSchema: { type: 'object', required: ['description'], properties: { description: { type: 'string' } } },
|
|
2656
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2657
|
+
destructive: false,
|
|
2658
|
+
requiresConfirmation: false,
|
|
2659
|
+
validators: ['panel-exists', 'panel-description-valid'],
|
|
2660
|
+
affectedPaths: ['panels[].description'],
|
|
2661
|
+
submissionImpact: 'config-only',
|
|
2662
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
operationId: 'panel.icon.set',
|
|
2666
|
+
title: 'Set panel icon',
|
|
2667
|
+
scope: 'layout',
|
|
2668
|
+
targetKind: 'panelHeader',
|
|
2669
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2670
|
+
inputSchema: { type: 'object', required: ['icon'], properties: { icon: { type: 'string' } } },
|
|
2671
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2672
|
+
destructive: false,
|
|
2673
|
+
requiresConfirmation: false,
|
|
2674
|
+
validators: ['panel-exists', 'panel-icon-valid', 'editor-runtime-round-trip'],
|
|
2675
|
+
affectedPaths: ['panels[].icon'],
|
|
2676
|
+
submissionImpact: 'config-only',
|
|
2677
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2678
|
+
},
|
|
2679
|
+
{
|
|
2680
|
+
operationId: 'panel.order.set',
|
|
2681
|
+
title: 'Reorder panels',
|
|
2682
|
+
scope: 'layout',
|
|
2683
|
+
targetKind: 'panel',
|
|
2684
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2685
|
+
inputSchema: { type: 'object', required: ['beforePanelId'], properties: { beforePanelId: { type: 'string' } } },
|
|
2686
|
+
effects: [{ kind: 'reorder-by-key', path: 'panels[]', key: 'id' }],
|
|
2687
|
+
destructive: false,
|
|
2688
|
+
requiresConfirmation: false,
|
|
2689
|
+
validators: ['panel-exists', 'panel-order-deterministic'],
|
|
2690
|
+
affectedPaths: ['panels[]'],
|
|
2691
|
+
submissionImpact: 'config-only',
|
|
2692
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
operationId: 'panel.disabled.set',
|
|
2696
|
+
title: 'Set disabled state',
|
|
2697
|
+
scope: 'interaction',
|
|
2698
|
+
targetKind: 'disabledState',
|
|
2699
|
+
target: { kind: 'disabledState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2700
|
+
inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
|
|
2701
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2702
|
+
destructive: false,
|
|
2703
|
+
requiresConfirmation: false,
|
|
2704
|
+
validators: ['panel-exists', 'disabled-expanded-compatible'],
|
|
2705
|
+
affectedPaths: ['panels[].disabled', 'panels[].expanded'],
|
|
2706
|
+
submissionImpact: 'config-only',
|
|
2707
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2708
|
+
},
|
|
2709
|
+
{
|
|
2710
|
+
operationId: 'behavior.multiExpand.set',
|
|
2711
|
+
title: 'Set multi-expand behavior',
|
|
2712
|
+
scope: 'interaction',
|
|
2713
|
+
targetKind: 'behavior',
|
|
2714
|
+
target: { kind: 'behavior', resolver: 'expansion-behavior-config', ambiguityPolicy: 'fail', required: true },
|
|
2715
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2716
|
+
effects: [{
|
|
2717
|
+
kind: 'compile-domain-patch',
|
|
2718
|
+
handler: 'expansion-multi-expand-set',
|
|
2719
|
+
handlerContract: {
|
|
2720
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2721
|
+
writes: ['accordion.multi', 'panels[].expanded'],
|
|
2722
|
+
identityKeys: ['panels[].id'],
|
|
2723
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2724
|
+
failureModes: ['multiple-expanded-panels-conflict', 'panel-id-missing'],
|
|
2725
|
+
description: 'Sets accordion.multi and collapses competing expanded panels when switching to single-expand behavior.',
|
|
2726
|
+
},
|
|
2727
|
+
}],
|
|
2728
|
+
destructive: false,
|
|
2729
|
+
requiresConfirmation: false,
|
|
2730
|
+
validators: ['multi-expand-default-compatible', 'accordion-values-valid', 'editor-runtime-round-trip'],
|
|
2731
|
+
affectedPaths: ['accordion.multi', 'panels[].expanded'],
|
|
2732
|
+
submissionImpact: 'config-only',
|
|
2733
|
+
preconditions: ['config-initialized'],
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
operationId: 'behavior.defaultExpanded.set',
|
|
2737
|
+
title: 'Set default expanded panel',
|
|
2738
|
+
scope: 'interaction',
|
|
2739
|
+
targetKind: 'expandedState',
|
|
2740
|
+
target: { kind: 'expandedState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2741
|
+
inputSchema: {
|
|
2742
|
+
type: 'object',
|
|
2743
|
+
required: ['expanded'],
|
|
2744
|
+
properties: {
|
|
2745
|
+
expanded: { type: 'boolean' },
|
|
2746
|
+
collapseOthers: { type: 'boolean' },
|
|
2747
|
+
},
|
|
2748
|
+
},
|
|
2749
|
+
effects: [{
|
|
2750
|
+
kind: 'compile-domain-patch',
|
|
2751
|
+
handler: 'expansion-default-expanded-upsert',
|
|
2752
|
+
handlerContract: {
|
|
2753
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded', 'panels[].disabled'],
|
|
2754
|
+
writes: ['panels[].expanded'],
|
|
2755
|
+
identityKeys: ['panels[].id'],
|
|
2756
|
+
inputSchema: {
|
|
2757
|
+
type: 'object',
|
|
2758
|
+
required: ['panelId', 'expanded'],
|
|
2759
|
+
properties: {
|
|
2760
|
+
panelId: { type: 'string' },
|
|
2761
|
+
expanded: { type: 'boolean' },
|
|
2762
|
+
collapseOthers: { type: 'boolean' },
|
|
2763
|
+
},
|
|
2764
|
+
},
|
|
2765
|
+
failureModes: ['panel-not-found', 'panel-disabled', 'single-expand-conflict'],
|
|
2766
|
+
description: 'Sets a panel expanded state and collapses competing panels when accordion.multi is false.',
|
|
2767
|
+
},
|
|
2768
|
+
}],
|
|
2769
|
+
validators: ['panel-exists', 'default-expanded-panel-exists', 'multi-expand-default-compatible', 'disabled-expanded-compatible'],
|
|
2770
|
+
destructive: false,
|
|
2771
|
+
requiresConfirmation: false,
|
|
2772
|
+
affectedPaths: ['panels[].expanded', 'accordion.multi'],
|
|
2773
|
+
submissionImpact: 'config-only',
|
|
2774
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2775
|
+
},
|
|
2776
|
+
{
|
|
2777
|
+
operationId: 'panel.content.set',
|
|
2778
|
+
title: 'Set panel content',
|
|
2779
|
+
scope: 'layout',
|
|
2780
|
+
targetKind: 'panelContent',
|
|
2781
|
+
target: { kind: 'panelContent', resolver: 'panel-content-by-id', ambiguityPolicy: 'fail', required: true },
|
|
2782
|
+
inputSchema: panelPatchSchema,
|
|
2783
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2784
|
+
destructive: false,
|
|
2785
|
+
requiresConfirmation: false,
|
|
2786
|
+
validators: ['panel-exists', 'panel-content-valid', 'lazy-content-compatible', 'nested-widget-contract-delegated'],
|
|
2787
|
+
affectedPaths: ['panels[].content', 'panels[].widgets', 'panels[].actionButtons'],
|
|
2788
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
2789
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2790
|
+
},
|
|
2791
|
+
],
|
|
2792
|
+
validators: [
|
|
2793
|
+
{ validatorId: 'panel-id-unique', level: 'error', code: 'PEXP001', description: 'Panel ids must be unique and stable within config.panels[].' },
|
|
2794
|
+
{ validatorId: 'panel-exists', level: 'error', code: 'PEXP002', description: 'Target panel must exist before applying the operation.' },
|
|
2795
|
+
{ validatorId: 'panel-order-deterministic', level: 'error', code: 'PEXP003', description: 'Panel ordering must use stable ids, not transient array index as identity.' },
|
|
2796
|
+
{ validatorId: 'panel-title-valid', level: 'error', code: 'PEXP004', description: 'Panel title must be a non-empty text value after localization/domain projection.' },
|
|
2797
|
+
{ validatorId: 'panel-description-valid', level: 'warning', code: 'PEXP005', description: 'Panel description should remain plain header-support text and not replace panel content.' },
|
|
2798
|
+
{ validatorId: 'panel-icon-valid', level: 'warning', code: 'PEXP006', description: 'Panel icon metadata must remain compatible with PraxisIconDirective and editor round-trip.' },
|
|
2799
|
+
{ validatorId: 'panel-content-valid', level: 'error', code: 'PEXP007', description: 'Panel content must remain valid FieldMetadata[], WidgetDefinition[] or action button metadata.' },
|
|
2800
|
+
{ validatorId: 'panel-content-removal-confirmed', level: 'error', code: 'PEXP008', description: 'Removing a panel with fields, widgets or action buttons is destructive and requires confirmation.' },
|
|
2801
|
+
{ validatorId: 'default-expanded-panel-exists', level: 'error', code: 'PEXP009', description: 'Default expanded state must reference an existing panel id.' },
|
|
2802
|
+
{ validatorId: 'default-expanded-removal-safe', level: 'error', code: 'PEXP010', description: 'Removing an expanded panel requires deterministic replacement state or explicit confirmation.' },
|
|
2803
|
+
{ validatorId: 'multi-expand-default-compatible', level: 'error', code: 'PEXP011', description: 'When accordion.multi is false, at most one panel may be marked expanded by default.' },
|
|
2804
|
+
{ validatorId: 'disabled-expanded-compatible', level: 'warning', code: 'PEXP012', description: 'A disabled panel should not be the only expanded/default focus target without explicit intent.' },
|
|
2805
|
+
{ validatorId: 'accordion-values-valid', level: 'error', code: 'PEXP013', description: 'Accordion behavior values must match ExpansionMetadata and Angular Material expansion bindings.' },
|
|
2806
|
+
{ validatorId: 'lazy-content-compatible', level: 'info', code: 'PEXP015', description: 'Panel content remains lazy through matExpansionPanelContent and should not require eager child runtime state.' },
|
|
2807
|
+
{ validatorId: 'nested-widget-contract-delegated', level: 'info', code: 'PEXP016', description: 'Nested widget content remains governed by child component contracts and component-port nestedPath semantics.' },
|
|
2808
|
+
{ validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PEXP017', description: 'Settings Panel editor, runtime persistence and registry projection must preserve panel ids, order, icons and expanded state.' },
|
|
2809
|
+
],
|
|
2810
|
+
roundTripRequirements: [
|
|
2811
|
+
'Operations must preserve stable panel ids; array index may be used only as a resolver fallback, never as canonical identity.',
|
|
2812
|
+
'Settings Panel editor, runtime persistence and registry projection must round-trip ExpansionMetadata without losing panel ids, order, icons or expanded state.',
|
|
2813
|
+
'When accordion.multi is false, authoring must collapse competing panels or fail validation instead of producing multiple default-expanded panels.',
|
|
2814
|
+
'Lazy panel content remains represented by panels[].content, panels[].widgets and panels[].actionButtons; authoring cannot require eager child component instances.',
|
|
2815
|
+
'Nested widgets remain delegated through WidgetDefinition and component-port nestedPath semantics instead of being redefined by the expansion contract.',
|
|
2816
|
+
],
|
|
2817
|
+
examples: [
|
|
2818
|
+
{ id: 'add-summary-panel', request: 'Add a summary panel before the audit panel.', operationId: 'panel.add', params: { id: 'summary', title: 'Summary' }, isPositive: true },
|
|
2819
|
+
{ id: 'rename-details-panel', request: 'Rename details to Account details.', operationId: 'panel.title.set', target: 'details', params: { title: 'Account details' }, isPositive: true },
|
|
2820
|
+
{ id: 'describe-audit-panel', request: 'Set audit panel description to Recent changes.', operationId: 'panel.description.set', target: 'audit', params: { description: 'Recent changes' }, isPositive: true },
|
|
2821
|
+
{ id: 'set-panel-icon', request: 'Use the info icon on the summary panel.', operationId: 'panel.icon.set', target: 'summary', params: { icon: 'info' }, isPositive: true },
|
|
2822
|
+
{ id: 'set-default-expanded-panel', request: 'Open the summary panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'summary', params: { expanded: true, collapseOthers: true }, isPositive: true },
|
|
2823
|
+
{ id: 'enable-multi-expand', request: 'Allow multiple panels to stay open.', operationId: 'behavior.multiExpand.set', params: { multi: true }, isPositive: true },
|
|
2824
|
+
{ id: 'disable-archive-panel', request: 'Disable the archive panel.', operationId: 'panel.disabled.set', target: 'archive', params: { disabled: true }, isPositive: true },
|
|
2825
|
+
{ id: 'reject-missing-default-expanded', request: 'Open the missing panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'missing', params: { expanded: true }, isPositive: false },
|
|
2826
|
+
{ id: 'reject-duplicate-panel-id', request: 'Add another panel with id summary.', operationId: 'panel.add', params: { id: 'summary', title: 'Duplicate summary' }, isPositive: false },
|
|
2827
|
+
{ id: 'confirm-remove-content-panel', request: 'Remove the details panel that contains widgets.', operationId: 'panel.remove', target: 'details', params: { replacementExpandedPanelId: 'summary' }, isPositive: true },
|
|
2828
|
+
],
|
|
2829
|
+
};
|
|
2830
|
+
|
|
1694
2831
|
/*
|
|
1695
2832
|
* Public API Surface of praxis-expansion
|
|
1696
2833
|
*/
|
|
@@ -1699,4 +2836,4 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1699
2836
|
* Generated bundle index. Do not edit.
|
|
1700
2837
|
*/
|
|
1701
2838
|
|
|
1702
|
-
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|
|
2839
|
+
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, PraxisExpansionWidgetConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|