@praxisui/expansion 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 +63 -1
- package/fesm2022/praxisui-expansion.mjs +1177 -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, 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,280 @@ 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
|
+
const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
403
|
+
const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
|
|
404
|
+
if (recommendedFlow === 'shared_rule_authoring')
|
|
405
|
+
return true;
|
|
406
|
+
return [
|
|
407
|
+
'regra',
|
|
408
|
+
'politica',
|
|
409
|
+
'policy',
|
|
410
|
+
'compliance',
|
|
411
|
+
'lgpd',
|
|
412
|
+
'privacidade',
|
|
413
|
+
'aprovacao',
|
|
414
|
+
'aprovar',
|
|
415
|
+
'publicar',
|
|
416
|
+
'materializar',
|
|
417
|
+
'enforcement',
|
|
418
|
+
'validacao de negocio',
|
|
419
|
+
'validar negocio',
|
|
420
|
+
'elegibilidade',
|
|
421
|
+
'permissao',
|
|
422
|
+
'acesso',
|
|
423
|
+
].some((term) => normalized.includes(term));
|
|
424
|
+
}
|
|
425
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
426
|
+
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.';
|
|
427
|
+
return {
|
|
428
|
+
state: 'clarification',
|
|
429
|
+
phase: 'clarify',
|
|
430
|
+
sessionId: request.sessionId,
|
|
431
|
+
assistantMessage: message,
|
|
432
|
+
statusText: 'Handoff governado necessario.',
|
|
433
|
+
canApply: false,
|
|
434
|
+
quickReplies: [
|
|
435
|
+
{
|
|
436
|
+
id: 'shared-rule-handoff',
|
|
437
|
+
label: 'Continuar como regra governada',
|
|
438
|
+
prompt,
|
|
439
|
+
kind: 'shared-rule-handoff',
|
|
440
|
+
description: 'Criar intake de domain-rules em vez de aplicar patch local no expansion.',
|
|
441
|
+
icon: 'rule',
|
|
442
|
+
tone: 'warning',
|
|
443
|
+
contextHints: {
|
|
444
|
+
flowId: 'shared_rule_authoring',
|
|
445
|
+
source: 'praxis-expansion',
|
|
446
|
+
recommendedAction: 'domain-rules/intake',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
clarificationQuestions: [
|
|
451
|
+
{
|
|
452
|
+
id: 'expansion-governed-rule-confirmation',
|
|
453
|
+
type: 'confirm',
|
|
454
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
455
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
456
|
+
required: true,
|
|
457
|
+
options: [
|
|
458
|
+
{
|
|
459
|
+
id: 'shared-rule-handoff',
|
|
460
|
+
label: 'Sim, continuar governado',
|
|
461
|
+
value: prompt,
|
|
462
|
+
description: 'Nao aplicar como patch local do expansion.',
|
|
463
|
+
contextHints: { flowId: 'shared_rule_authoring', source: 'praxis-expansion' },
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
diagnostics: {
|
|
469
|
+
governedDecisionHandoff: {
|
|
470
|
+
flowId: 'shared_rule_authoring',
|
|
471
|
+
sourcePrompt: prompt,
|
|
472
|
+
sourceComponent: 'praxis-expansion',
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
optionalJsonObject(value) {
|
|
478
|
+
if (value === undefined || value === null)
|
|
479
|
+
return undefined;
|
|
480
|
+
const object = this.toAiJsonObject(value);
|
|
481
|
+
return Object.keys(object).length ? object : undefined;
|
|
482
|
+
}
|
|
483
|
+
toAiJsonObject(value) {
|
|
484
|
+
const record = this.toRecord(value);
|
|
485
|
+
if (!record)
|
|
486
|
+
return {};
|
|
487
|
+
try {
|
|
488
|
+
return JSON.parse(JSON.stringify(record));
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return {};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
toRecord(value) {
|
|
495
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
496
|
+
? value
|
|
497
|
+
: null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
187
501
|
class PraxisExpansion {
|
|
188
502
|
config;
|
|
189
503
|
expansionId;
|
|
@@ -211,9 +525,35 @@ class PraxisExpansion {
|
|
|
211
525
|
catch {
|
|
212
526
|
return undefined;
|
|
213
527
|
} })();
|
|
528
|
+
aiApi = inject(AiBackendApiService);
|
|
529
|
+
assistantSessions = inject(PraxisAssistantSessionRegistryService);
|
|
530
|
+
aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
|
|
531
|
+
aiAssistantSessionEffect = effect(() => {
|
|
532
|
+
const session = this.assistantSessions.activeSession();
|
|
533
|
+
if (!session || session.id !== this.resolveAiAssistantSessionId())
|
|
534
|
+
return;
|
|
535
|
+
if (!this.aiAssistantOpen) {
|
|
536
|
+
this.openAiAssistantFromSession(session);
|
|
537
|
+
}
|
|
538
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
|
|
214
539
|
warnedMissingId = false;
|
|
215
540
|
panelForms = new Map();
|
|
216
541
|
aiAdapter = new ExpansionAiAdapter(this);
|
|
542
|
+
aiAssistantOpen = false;
|
|
543
|
+
aiAssistantPrompt = '';
|
|
544
|
+
aiAssistantViewState = null;
|
|
545
|
+
aiAssistantLayout = createPraxisAssistantViewportLayout();
|
|
546
|
+
aiAssistantLabels = {
|
|
547
|
+
title: 'Copiloto semantico Praxis',
|
|
548
|
+
subtitle: 'Converse, revise e governe ajustes dos paineis.',
|
|
549
|
+
prompt: 'Mensagem',
|
|
550
|
+
promptPlaceholder: 'Descreva o ajuste que voce precisa nos paineis.',
|
|
551
|
+
emptyConversation: 'Diga o que voce quer alterar no expansion.',
|
|
552
|
+
submit: 'Interpretar pedido',
|
|
553
|
+
apply: 'Aplicar ajuste',
|
|
554
|
+
};
|
|
555
|
+
aiAssistantController = null;
|
|
556
|
+
aiAssistantStateSubscription = null;
|
|
217
557
|
accordionRef;
|
|
218
558
|
panels;
|
|
219
559
|
injectedDefaults = inject(MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, { optional: true });
|
|
@@ -237,6 +577,10 @@ class PraxisExpansion {
|
|
|
237
577
|
this.persistConfig(this.config);
|
|
238
578
|
}
|
|
239
579
|
}
|
|
580
|
+
ngOnDestroy() {
|
|
581
|
+
this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
|
|
582
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
583
|
+
}
|
|
240
584
|
styleCss() {
|
|
241
585
|
const t = this.config?.appearance?.tokens;
|
|
242
586
|
const appearance = this.config?.appearance;
|
|
@@ -476,6 +820,306 @@ class PraxisExpansion {
|
|
|
476
820
|
}
|
|
477
821
|
this.cdr.markForCheck();
|
|
478
822
|
}
|
|
823
|
+
openAiAssistant() {
|
|
824
|
+
this.initializeAiAssistantController();
|
|
825
|
+
this.aiAssistantOpen = true;
|
|
826
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
827
|
+
this.syncAiAssistantSession('active');
|
|
828
|
+
this.cdr.markForCheck();
|
|
829
|
+
}
|
|
830
|
+
openAiAssistantFromSession(session) {
|
|
831
|
+
if (session.id !== this.resolveAiAssistantSessionId())
|
|
832
|
+
return;
|
|
833
|
+
this.initializeAiAssistantController();
|
|
834
|
+
this.aiAssistantOpen = true;
|
|
835
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
836
|
+
this.syncAiAssistantSession('active');
|
|
837
|
+
this.cdr.markForCheck();
|
|
838
|
+
}
|
|
839
|
+
closeAiAssistant() {
|
|
840
|
+
this.aiAssistantOpen = false;
|
|
841
|
+
this.syncAiAssistantSession('minimized');
|
|
842
|
+
this.cdr.markForCheck();
|
|
843
|
+
}
|
|
844
|
+
onAiAssistantPromptChange(prompt) {
|
|
845
|
+
this.aiAssistantPrompt = prompt;
|
|
846
|
+
this.syncAiAssistantSession();
|
|
847
|
+
}
|
|
848
|
+
onAiAssistantSubmit(prompt) {
|
|
849
|
+
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
850
|
+
this.aiAssistantPrompt = '';
|
|
851
|
+
this.aiAssistantViewState = state;
|
|
852
|
+
this.syncAiAssistantSession();
|
|
853
|
+
this.cdr.markForCheck();
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
onAiAssistantApply() {
|
|
857
|
+
this.aiAssistantController?.apply().subscribe((state) => {
|
|
858
|
+
this.aiAssistantViewState = state;
|
|
859
|
+
this.syncAiAssistantSession();
|
|
860
|
+
this.cdr.markForCheck();
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
onAiAssistantRetry() {
|
|
864
|
+
this.aiAssistantController?.retry().subscribe((state) => {
|
|
865
|
+
this.aiAssistantViewState = state;
|
|
866
|
+
this.syncAiAssistantSession();
|
|
867
|
+
this.cdr.markForCheck();
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
onAiAssistantCancel() {
|
|
871
|
+
this.aiAssistantController?.cancel().subscribe((state) => {
|
|
872
|
+
this.aiAssistantPrompt = '';
|
|
873
|
+
this.aiAssistantViewState = state;
|
|
874
|
+
this.syncAiAssistantSession();
|
|
875
|
+
this.cdr.markForCheck();
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
onAiAssistantQuickReply(reply) {
|
|
879
|
+
const controller = this.aiAssistantController;
|
|
880
|
+
if (!controller)
|
|
881
|
+
return;
|
|
882
|
+
const state = controller.snapshot();
|
|
883
|
+
const next$ = state.state === 'clarification'
|
|
884
|
+
? controller.answerClarification(reply.prompt)
|
|
885
|
+
: controller.submitPrompt(reply.prompt, {
|
|
886
|
+
kind: reply.kind || 'quick-reply',
|
|
887
|
+
id: reply.id,
|
|
888
|
+
value: reply.prompt,
|
|
889
|
+
});
|
|
890
|
+
next$.subscribe((nextState) => {
|
|
891
|
+
this.aiAssistantPrompt = '';
|
|
892
|
+
this.aiAssistantViewState = nextState;
|
|
893
|
+
this.syncAiAssistantSession();
|
|
894
|
+
this.cdr.markForCheck();
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
onAiAssistantEditMessage(message) {
|
|
898
|
+
this.aiAssistantPrompt = message.text;
|
|
899
|
+
this.cdr.markForCheck();
|
|
900
|
+
}
|
|
901
|
+
onAiAssistantResendMessage(message) {
|
|
902
|
+
this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
|
|
903
|
+
this.aiAssistantPrompt = '';
|
|
904
|
+
this.aiAssistantViewState = state;
|
|
905
|
+
this.syncAiAssistantSession();
|
|
906
|
+
this.cdr.markForCheck();
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
onAiAssistantLayoutChange(layout) {
|
|
910
|
+
this.aiAssistantLayout = layout;
|
|
911
|
+
}
|
|
912
|
+
buildAiAssistantContextItems() {
|
|
913
|
+
const panels = this.config?.panels ?? [];
|
|
914
|
+
const items = [
|
|
915
|
+
{
|
|
916
|
+
id: 'component',
|
|
917
|
+
label: 'Componente',
|
|
918
|
+
value: 'Expansion',
|
|
919
|
+
kind: 'component',
|
|
920
|
+
icon: 'unfold_more',
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
id: 'expansion-id',
|
|
924
|
+
label: 'Expansion',
|
|
925
|
+
value: this.safeAiAssistantExpansionId(),
|
|
926
|
+
kind: 'custom',
|
|
927
|
+
icon: 'tag',
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
id: 'panels',
|
|
931
|
+
label: 'Paineis',
|
|
932
|
+
value: String(panels.length),
|
|
933
|
+
kind: 'custom',
|
|
934
|
+
icon: 'view_agenda',
|
|
935
|
+
},
|
|
936
|
+
];
|
|
937
|
+
const expanded = panels.filter((panel) => panel.expanded).length;
|
|
938
|
+
if (expanded > 0) {
|
|
939
|
+
items.push({
|
|
940
|
+
id: 'expanded-panels',
|
|
941
|
+
label: 'Expandidos',
|
|
942
|
+
value: String(expanded),
|
|
943
|
+
kind: 'custom',
|
|
944
|
+
icon: 'expand_less',
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return items;
|
|
948
|
+
}
|
|
949
|
+
initializeAiAssistantController() {
|
|
950
|
+
if (this.aiAssistantController)
|
|
951
|
+
return;
|
|
952
|
+
const flow = new ExpansionAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
|
|
953
|
+
const controller = this.aiTurnOrchestrator.createController(flow, {
|
|
954
|
+
componentId: this.aiAdapter.componentId || 'praxis-expansion',
|
|
955
|
+
componentType: this.aiAdapter.componentType || 'expansion',
|
|
956
|
+
contextItems: this.buildAiAssistantContextItems(),
|
|
957
|
+
});
|
|
958
|
+
this.aiAssistantController = controller;
|
|
959
|
+
this.aiAssistantViewState = controller.snapshot();
|
|
960
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
961
|
+
this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
|
|
962
|
+
this.aiAssistantViewState = state;
|
|
963
|
+
this.syncAiAssistantSession();
|
|
964
|
+
this.cdr.markForCheck();
|
|
965
|
+
});
|
|
966
|
+
this.cdr.markForCheck();
|
|
967
|
+
}
|
|
968
|
+
buildAiAssistantContextSnapshot() {
|
|
969
|
+
const counts = this.collectAiAssistantCounts();
|
|
970
|
+
const panelNames = this.collectAiAssistantPanelNames();
|
|
971
|
+
return {
|
|
972
|
+
identity: {
|
|
973
|
+
sessionId: this.resolveAiAssistantSessionId(),
|
|
974
|
+
ownerId: this.resolveAiAssistantOwnerId(),
|
|
975
|
+
ownerType: 'expansion',
|
|
976
|
+
componentId: 'praxis-expansion',
|
|
977
|
+
componentType: 'expansion',
|
|
978
|
+
routeKey: this.resolveAiAssistantRouteKey(),
|
|
979
|
+
},
|
|
980
|
+
target: {
|
|
981
|
+
kind: 'component',
|
|
982
|
+
id: this.resolveAiAssistantOwnerId(),
|
|
983
|
+
label: this.safeAiAssistantExpansionId(),
|
|
984
|
+
metadata: {
|
|
985
|
+
expansionId: this.safeAiAssistantExpansionId(),
|
|
986
|
+
hasCustomization: !!this.enableCustomization,
|
|
987
|
+
},
|
|
988
|
+
},
|
|
989
|
+
contextItems: this.buildAiAssistantContextItems().map((item) => ({
|
|
990
|
+
id: item.id,
|
|
991
|
+
label: item.label,
|
|
992
|
+
value: item.value || '',
|
|
993
|
+
kind: item.kind,
|
|
994
|
+
})),
|
|
995
|
+
mode: 'agentic-authoring',
|
|
996
|
+
authoringManifestRef: {
|
|
997
|
+
componentId: 'praxis-expansion',
|
|
998
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
999
|
+
},
|
|
1000
|
+
schemaFields: panelNames.length ? panelNames : undefined,
|
|
1001
|
+
dataProfileDigest: {
|
|
1002
|
+
summary: `${counts.panelCount} painel(is), ${counts.contentPanelCount} com campo(s), ${counts.widgetPanelCount} com widget(s)`,
|
|
1003
|
+
counts,
|
|
1004
|
+
},
|
|
1005
|
+
runtimeStateDigest: {
|
|
1006
|
+
summary: `Expansion ${this.config?.accordion?.multi ? 'multi' : 'single'}, ${counts.expandedCount} painel(is) expandido(s)`,
|
|
1007
|
+
fields: [
|
|
1008
|
+
'panels',
|
|
1009
|
+
'accordion',
|
|
1010
|
+
'content',
|
|
1011
|
+
'widgets',
|
|
1012
|
+
],
|
|
1013
|
+
},
|
|
1014
|
+
capabilityRefs: [
|
|
1015
|
+
{
|
|
1016
|
+
id: 'expansion.component-edit-plan',
|
|
1017
|
+
label: 'Plano de edicao de paineis',
|
|
1018
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
1019
|
+
risk: 'medium',
|
|
1020
|
+
},
|
|
1021
|
+
],
|
|
1022
|
+
governanceHints: [
|
|
1023
|
+
{
|
|
1024
|
+
kind: 'business-rule-boundary',
|
|
1025
|
+
label: 'Regras compartilhadas exigem governanca',
|
|
1026
|
+
reason: 'Politicas de acesso, validacoes reutilizaveis e compliance nao devem ser aplicadas como patch local do expansion.',
|
|
1027
|
+
risk: 'high',
|
|
1028
|
+
},
|
|
1029
|
+
],
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
syncAiAssistantSession(visibility = null) {
|
|
1033
|
+
if (!this.enableCustomization)
|
|
1034
|
+
return;
|
|
1035
|
+
if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
|
|
1036
|
+
return;
|
|
1037
|
+
const state = this.aiAssistantViewState;
|
|
1038
|
+
this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
|
|
1039
|
+
title: 'Copiloto semantico Praxis',
|
|
1040
|
+
summary: this.resolveAiAssistantSummary(),
|
|
1041
|
+
mode: state?.mode || 'agentic-authoring',
|
|
1042
|
+
state: state?.state || 'idle',
|
|
1043
|
+
visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
|
|
1044
|
+
badge: this.resolveAiAssistantBadge(),
|
|
1045
|
+
icon: this.resolveAiAssistantIcon(),
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
hasAiAssistantSessionState() {
|
|
1049
|
+
return !!this.aiAssistantPrompt.trim()
|
|
1050
|
+
|| !!this.aiAssistantViewState?.messages?.length
|
|
1051
|
+
|| !!this.aiAssistantViewState?.quickReplies?.length
|
|
1052
|
+
|| !!this.aiAssistantViewState?.pendingPatch
|
|
1053
|
+
|| !!this.aiAssistantViewState?.statusText?.trim()
|
|
1054
|
+
|| !!this.aiAssistantViewState?.errorText?.trim();
|
|
1055
|
+
}
|
|
1056
|
+
resolveAiAssistantSessionId() {
|
|
1057
|
+
return `expansion:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
|
|
1058
|
+
}
|
|
1059
|
+
resolveAiAssistantOwnerId() {
|
|
1060
|
+
return (this.componentInstanceId || this.safeAiAssistantExpansionId() || 'expansion').trim() || 'expansion';
|
|
1061
|
+
}
|
|
1062
|
+
safeAiAssistantExpansionId() {
|
|
1063
|
+
return String(this.expansionId || '').trim();
|
|
1064
|
+
}
|
|
1065
|
+
resolveAiAssistantRouteKey() {
|
|
1066
|
+
const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
|
|
1067
|
+
return routePath || 'local';
|
|
1068
|
+
}
|
|
1069
|
+
resolveAiAssistantSummary() {
|
|
1070
|
+
const status = this.aiAssistantViewState?.statusText?.trim();
|
|
1071
|
+
if (status)
|
|
1072
|
+
return status;
|
|
1073
|
+
const error = this.aiAssistantViewState?.errorText?.trim();
|
|
1074
|
+
if (error)
|
|
1075
|
+
return error;
|
|
1076
|
+
const prompt = this.aiAssistantPrompt.trim();
|
|
1077
|
+
if (prompt)
|
|
1078
|
+
return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
|
|
1079
|
+
const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
|
|
1080
|
+
.find((message) => message.role === 'assistant' || message.role === 'user');
|
|
1081
|
+
if (lastMessage?.text) {
|
|
1082
|
+
return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
|
|
1083
|
+
}
|
|
1084
|
+
return 'Assistente contextual dos paineis.';
|
|
1085
|
+
}
|
|
1086
|
+
resolveAiAssistantBadge() {
|
|
1087
|
+
const state = this.aiAssistantViewState?.state;
|
|
1088
|
+
if (state === 'error')
|
|
1089
|
+
return 'erro';
|
|
1090
|
+
if (state === 'clarification')
|
|
1091
|
+
return 'revisar';
|
|
1092
|
+
if (state === 'review')
|
|
1093
|
+
return 'preview';
|
|
1094
|
+
if (state === 'success')
|
|
1095
|
+
return 'ok';
|
|
1096
|
+
return undefined;
|
|
1097
|
+
}
|
|
1098
|
+
resolveAiAssistantIcon() {
|
|
1099
|
+
const state = this.aiAssistantViewState?.state;
|
|
1100
|
+
if (state === 'error')
|
|
1101
|
+
return 'error';
|
|
1102
|
+
if (state === 'clarification')
|
|
1103
|
+
return 'rule';
|
|
1104
|
+
if (state === 'review')
|
|
1105
|
+
return 'rate_review';
|
|
1106
|
+
return 'auto_awesome';
|
|
1107
|
+
}
|
|
1108
|
+
collectAiAssistantPanelNames() {
|
|
1109
|
+
return Array.from(new Set((this.config?.panels ?? [])
|
|
1110
|
+
.map((panel, index) => panel.id || panel.title || `panel-${index + 1}`)
|
|
1111
|
+
.filter((name) => typeof name === 'string' && !!name.trim())));
|
|
1112
|
+
}
|
|
1113
|
+
collectAiAssistantCounts() {
|
|
1114
|
+
const panels = this.config?.panels ?? [];
|
|
1115
|
+
return {
|
|
1116
|
+
panelCount: panels.length,
|
|
1117
|
+
contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
|
|
1118
|
+
widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
|
|
1119
|
+
actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
|
|
1120
|
+
expandedCount: panels.filter((panel) => !!panel.expanded).length,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
479
1123
|
openEditor() {
|
|
480
1124
|
const key = this.storageKey() || this.expansionId || 'default';
|
|
481
1125
|
const ref = this.settings.open({
|
|
@@ -573,10 +1217,48 @@ class PraxisExpansion {
|
|
|
573
1217
|
|
|
574
1218
|
@if (enableCustomization) {
|
|
575
1219
|
<div class="expansion-ai-assistant">
|
|
576
|
-
<
|
|
1220
|
+
<button
|
|
1221
|
+
mat-mini-fab
|
|
1222
|
+
color="primary"
|
|
1223
|
+
type="button"
|
|
1224
|
+
class="expansion-ai-assistant-trigger"
|
|
1225
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1226
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1227
|
+
(click)="openAiAssistant()"
|
|
1228
|
+
>
|
|
1229
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1230
|
+
</button>
|
|
577
1231
|
</div>
|
|
578
1232
|
}
|
|
579
1233
|
|
|
1234
|
+
<praxis-ai-assistant-shell
|
|
1235
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
1236
|
+
[labels]="aiAssistantLabels"
|
|
1237
|
+
[mode]="aiAssistantViewState.mode"
|
|
1238
|
+
[state]="aiAssistantViewState.state"
|
|
1239
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1240
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1241
|
+
[messages]="aiAssistantViewState.messages"
|
|
1242
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1243
|
+
[prompt]="aiAssistantPrompt"
|
|
1244
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1245
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1246
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1247
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1248
|
+
[layout]="aiAssistantLayout"
|
|
1249
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1250
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1251
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1252
|
+
(apply)="onAiAssistantApply()"
|
|
1253
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1254
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1255
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1256
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1257
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1258
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1259
|
+
(close)="closeAiAssistant()"
|
|
1260
|
+
></praxis-ai-assistant-shell>
|
|
1261
|
+
|
|
580
1262
|
@if (hasMultiple()) {
|
|
581
1263
|
<mat-accordion
|
|
582
1264
|
#accordion
|
|
@@ -603,8 +1285,9 @@ class PraxisExpansion {
|
|
|
603
1285
|
>
|
|
604
1286
|
<mat-expansion-panel-header
|
|
605
1287
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
606
|
-
|
|
607
|
-
|
|
1288
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1289
|
+
>
|
|
1290
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
608
1291
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
609
1292
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
610
1293
|
</mat-expansion-panel-header>
|
|
@@ -660,8 +1343,9 @@ class PraxisExpansion {
|
|
|
660
1343
|
>
|
|
661
1344
|
<mat-expansion-panel-header
|
|
662
1345
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
663
|
-
|
|
664
|
-
|
|
1346
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1347
|
+
>
|
|
1348
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
665
1349
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
666
1350
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
667
1351
|
</mat-expansion-panel-header>
|
|
@@ -718,7 +1402,7 @@ class PraxisExpansion {
|
|
|
718
1402
|
>
|
|
719
1403
|
<mat-icon fontIcon="edit"></mat-icon>
|
|
720
1404
|
</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:
|
|
1405
|
+
`, 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"] }, { 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
1406
|
}
|
|
723
1407
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansion, decorators: [{
|
|
724
1408
|
type: Component,
|
|
@@ -730,7 +1414,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
730
1414
|
DynamicFieldLoaderDirective,
|
|
731
1415
|
DynamicWidgetLoaderDirective,
|
|
732
1416
|
PraxisIconDirective,
|
|
733
|
-
|
|
1417
|
+
PraxisAiAssistantShellComponent,
|
|
734
1418
|
], template: `
|
|
735
1419
|
<div
|
|
736
1420
|
class="praxis-expansion-root"
|
|
@@ -745,10 +1429,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
745
1429
|
|
|
746
1430
|
@if (enableCustomization) {
|
|
747
1431
|
<div class="expansion-ai-assistant">
|
|
748
|
-
<
|
|
1432
|
+
<button
|
|
1433
|
+
mat-mini-fab
|
|
1434
|
+
color="primary"
|
|
1435
|
+
type="button"
|
|
1436
|
+
class="expansion-ai-assistant-trigger"
|
|
1437
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1438
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1439
|
+
(click)="openAiAssistant()"
|
|
1440
|
+
>
|
|
1441
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1442
|
+
</button>
|
|
749
1443
|
</div>
|
|
750
1444
|
}
|
|
751
1445
|
|
|
1446
|
+
<praxis-ai-assistant-shell
|
|
1447
|
+
*ngIf="aiAssistantOpen && aiAssistantViewState"
|
|
1448
|
+
[labels]="aiAssistantLabels"
|
|
1449
|
+
[mode]="aiAssistantViewState.mode"
|
|
1450
|
+
[state]="aiAssistantViewState.state"
|
|
1451
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1452
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1453
|
+
[messages]="aiAssistantViewState.messages"
|
|
1454
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1455
|
+
[prompt]="aiAssistantPrompt"
|
|
1456
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1457
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1458
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1459
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1460
|
+
[layout]="aiAssistantLayout"
|
|
1461
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1462
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1463
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1464
|
+
(apply)="onAiAssistantApply()"
|
|
1465
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1466
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1467
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1468
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1469
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1470
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1471
|
+
(close)="closeAiAssistant()"
|
|
1472
|
+
></praxis-ai-assistant-shell>
|
|
1473
|
+
|
|
752
1474
|
@if (hasMultiple()) {
|
|
753
1475
|
<mat-accordion
|
|
754
1476
|
#accordion
|
|
@@ -775,8 +1497,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
775
1497
|
>
|
|
776
1498
|
<mat-expansion-panel-header
|
|
777
1499
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
778
|
-
|
|
779
|
-
|
|
1500
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1501
|
+
>
|
|
1502
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
780
1503
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
781
1504
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
782
1505
|
</mat-expansion-panel-header>
|
|
@@ -832,8 +1555,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
832
1555
|
>
|
|
833
1556
|
<mat-expansion-panel-header
|
|
834
1557
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
835
|
-
|
|
836
|
-
|
|
1558
|
+
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
1559
|
+
>
|
|
1560
|
+
<mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
|
|
837
1561
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
838
1562
|
<mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
|
|
839
1563
|
</mat-expansion-panel-header>
|
|
@@ -890,7 +1614,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
890
1614
|
>
|
|
891
1615
|
<mat-icon fontIcon="edit"></mat-icon>
|
|
892
1616
|
</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"] }]
|
|
1617
|
+
`, 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
1618
|
}], propDecorators: { config: [{
|
|
895
1619
|
type: Input
|
|
896
1620
|
}], expansionId: [{
|
|
@@ -1099,7 +1823,7 @@ class PraxisExpansionConfigEditor {
|
|
|
1099
1823
|
}
|
|
1100
1824
|
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
1825
|
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">
|
|
1826
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1103
1827
|
<section class="sec">
|
|
1104
1828
|
<h4>Aparência</h4>
|
|
1105
1829
|
<div class="grid two">
|
|
@@ -1321,6 +2045,9 @@ class PraxisExpansionConfigEditor {
|
|
|
1321
2045
|
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
1322
2046
|
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
1323
2047
|
</mat-form-field>
|
|
2048
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2049
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2050
|
+
</mat-form-field>
|
|
1324
2051
|
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
1325
2052
|
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
1326
2053
|
</mat-form-field>
|
|
@@ -1349,7 +2076,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1349
2076
|
MatTooltipModule,
|
|
1350
2077
|
PraxisIconDirective,
|
|
1351
2078
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1352
|
-
<div class="pdx-expansion-editor">
|
|
2079
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1353
2080
|
<section class="sec">
|
|
1354
2081
|
<h4>Aparência</h4>
|
|
1355
2082
|
<div class="grid two">
|
|
@@ -1571,6 +2298,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1571
2298
|
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
1572
2299
|
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
1573
2300
|
</mat-form-field>
|
|
2301
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2302
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2303
|
+
</mat-form-field>
|
|
1574
2304
|
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
1575
2305
|
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
1576
2306
|
</mat-form-field>
|
|
@@ -1595,6 +2325,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1595
2325
|
type: Input
|
|
1596
2326
|
}] } });
|
|
1597
2327
|
|
|
2328
|
+
class PraxisExpansionWidgetConfigEditor {
|
|
2329
|
+
inputs = null;
|
|
2330
|
+
widgetKey;
|
|
2331
|
+
expansionEditor;
|
|
2332
|
+
isDirty$ = new BehaviorSubject(false);
|
|
2333
|
+
isValid$ = new BehaviorSubject(true);
|
|
2334
|
+
isBusy$ = new BehaviorSubject(false);
|
|
2335
|
+
subscription = new Subscription();
|
|
2336
|
+
emptyConfig = {};
|
|
2337
|
+
get config() {
|
|
2338
|
+
return this.inputs?.config ?? this.emptyConfig;
|
|
2339
|
+
}
|
|
2340
|
+
get expansionId() {
|
|
2341
|
+
return this.inputs?.expansionId ?? this.widgetKey;
|
|
2342
|
+
}
|
|
2343
|
+
ngAfterViewInit() {
|
|
2344
|
+
if (!this.expansionEditor) {
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
this.subscription.add(this.expansionEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
|
|
2348
|
+
this.subscription.add(this.expansionEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
|
|
2349
|
+
this.subscription.add(this.expansionEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
|
|
2350
|
+
}
|
|
2351
|
+
ngOnDestroy() {
|
|
2352
|
+
this.subscription.unsubscribe();
|
|
2353
|
+
}
|
|
2354
|
+
getSettingsValue() {
|
|
2355
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2356
|
+
}
|
|
2357
|
+
onSave() {
|
|
2358
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2359
|
+
}
|
|
2360
|
+
reset() {
|
|
2361
|
+
this.expansionEditor?.reset();
|
|
2362
|
+
}
|
|
2363
|
+
buildValue(config) {
|
|
2364
|
+
return {
|
|
2365
|
+
inputs: {
|
|
2366
|
+
...(this.inputs ?? {}),
|
|
2367
|
+
config: config ?? this.config,
|
|
2368
|
+
...(this.expansionId ? { expansionId: this.expansionId } : {}),
|
|
2369
|
+
},
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2373
|
+
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: `
|
|
2374
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2375
|
+
<praxis-expansion-config-editor
|
|
2376
|
+
#expansionEditor
|
|
2377
|
+
[config]="config"
|
|
2378
|
+
[expansionId]="expansionId"
|
|
2379
|
+
/>
|
|
2380
|
+
</section>
|
|
2381
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisExpansionConfigEditor, selector: "praxis-expansion-config-editor", inputs: ["config", "expansionId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2382
|
+
}
|
|
2383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, decorators: [{
|
|
2384
|
+
type: Component,
|
|
2385
|
+
args: [{
|
|
2386
|
+
selector: 'praxis-expansion-widget-config-editor',
|
|
2387
|
+
standalone: true,
|
|
2388
|
+
imports: [CommonModule, PraxisExpansionConfigEditor],
|
|
2389
|
+
template: `
|
|
2390
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2391
|
+
<praxis-expansion-config-editor
|
|
2392
|
+
#expansionEditor
|
|
2393
|
+
[config]="config"
|
|
2394
|
+
[expansionId]="expansionId"
|
|
2395
|
+
/>
|
|
2396
|
+
</section>
|
|
2397
|
+
`,
|
|
2398
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2399
|
+
}]
|
|
2400
|
+
}], propDecorators: { inputs: [{
|
|
2401
|
+
type: Input
|
|
2402
|
+
}], widgetKey: [{
|
|
2403
|
+
type: Input
|
|
2404
|
+
}], expansionEditor: [{
|
|
2405
|
+
type: ViewChild,
|
|
2406
|
+
args: ['expansionEditor']
|
|
2407
|
+
}] } });
|
|
2408
|
+
|
|
2409
|
+
const PRAXIS_EXPANSION_PORTS = [
|
|
2410
|
+
{
|
|
2411
|
+
id: 'config',
|
|
2412
|
+
label: 'Configuracao',
|
|
2413
|
+
direction: 'input',
|
|
2414
|
+
semanticKind: 'config-fragment',
|
|
2415
|
+
schema: {
|
|
2416
|
+
id: 'ExpansionMetadata',
|
|
2417
|
+
kind: 'ts-type',
|
|
2418
|
+
ref: 'ExpansionMetadata',
|
|
2419
|
+
},
|
|
2420
|
+
description: 'Fragmento canonico de configuracao dos paineis e dos widgets internos.',
|
|
2421
|
+
exposure: { public: true, group: 'config' },
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
id: 'widgetEvent',
|
|
2425
|
+
label: 'Evento interno de widget',
|
|
2426
|
+
direction: 'output',
|
|
2427
|
+
semanticKind: 'event',
|
|
2428
|
+
schema: {
|
|
2429
|
+
id: 'WidgetEventEnvelope',
|
|
2430
|
+
kind: 'ts-type',
|
|
2431
|
+
ref: 'WidgetEventEnvelope',
|
|
2432
|
+
},
|
|
2433
|
+
cardinality: 'stream',
|
|
2434
|
+
description: 'Bridge composta para transporte de eventos internos. As portas canonicas dos filhos sao resolvidas por component-port + nestedPath.',
|
|
2435
|
+
exposure: { public: true, advanced: true, group: 'composite' },
|
|
2436
|
+
},
|
|
2437
|
+
];
|
|
1598
2438
|
const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
1599
2439
|
id: 'praxis-expansion',
|
|
1600
2440
|
selector: 'praxis-expansion',
|
|
@@ -1602,6 +2442,14 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1602
2442
|
friendlyName: 'Praxis Expansion Panel',
|
|
1603
2443
|
description: 'Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.',
|
|
1604
2444
|
icon: 'unfold_more',
|
|
2445
|
+
authoringManifestRef: {
|
|
2446
|
+
componentId: 'praxis-expansion',
|
|
2447
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
2448
|
+
},
|
|
2449
|
+
configEditor: {
|
|
2450
|
+
component: PraxisExpansionWidgetConfigEditor,
|
|
2451
|
+
title: 'Configure expansion',
|
|
2452
|
+
},
|
|
1605
2453
|
inputs: [
|
|
1606
2454
|
{ name: 'config', type: 'ExpansionMetadata', label: 'Configuração', description: 'Configuração JSON (acordeão, aparência e painéis)' },
|
|
1607
2455
|
{ name: 'expansionId', type: 'string', label: 'ID', description: 'Identificador para persistência (obrigatório)' },
|
|
@@ -1676,6 +2524,7 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1676
2524
|
],
|
|
1677
2525
|
tags: ['widget', 'expansion', 'accordion', 'configurable'],
|
|
1678
2526
|
lib: '@praxisui/expansion',
|
|
2527
|
+
ports: PRAXIS_EXPANSION_PORTS,
|
|
1679
2528
|
};
|
|
1680
2529
|
function providePraxisExpansionMetadata() {
|
|
1681
2530
|
return {
|
|
@@ -1691,6 +2540,315 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1691
2540
|
return { provide: MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, useValue: opts };
|
|
1692
2541
|
}
|
|
1693
2542
|
|
|
2543
|
+
const panelItemSchema = {
|
|
2544
|
+
type: 'object',
|
|
2545
|
+
required: ['id', 'title'],
|
|
2546
|
+
properties: {
|
|
2547
|
+
id: { type: 'string' },
|
|
2548
|
+
title: { type: 'string' },
|
|
2549
|
+
description: { type: 'string' },
|
|
2550
|
+
icon: { type: 'string' },
|
|
2551
|
+
disabled: { type: 'boolean' },
|
|
2552
|
+
expanded: { type: 'boolean' },
|
|
2553
|
+
hideToggle: { type: 'boolean' },
|
|
2554
|
+
collapsedHeight: { type: 'string' },
|
|
2555
|
+
expandedHeight: { type: 'string' },
|
|
2556
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2557
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2558
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2559
|
+
},
|
|
2560
|
+
};
|
|
2561
|
+
const panelPatchSchema = {
|
|
2562
|
+
type: 'object',
|
|
2563
|
+
minProperties: 1,
|
|
2564
|
+
properties: {
|
|
2565
|
+
id: { type: 'string' },
|
|
2566
|
+
title: { type: 'string' },
|
|
2567
|
+
description: { type: 'string' },
|
|
2568
|
+
icon: { type: 'string' },
|
|
2569
|
+
disabled: { type: 'boolean' },
|
|
2570
|
+
expanded: { type: 'boolean' },
|
|
2571
|
+
hideToggle: { type: 'boolean' },
|
|
2572
|
+
collapsedHeight: { type: 'string' },
|
|
2573
|
+
expandedHeight: { type: 'string' },
|
|
2574
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2575
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2576
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2577
|
+
},
|
|
2578
|
+
};
|
|
2579
|
+
const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
|
|
2580
|
+
schemaVersion: '1.0.0',
|
|
2581
|
+
componentId: 'praxis-expansion',
|
|
2582
|
+
ownerPackage: '@praxisui/expansion',
|
|
2583
|
+
configSchemaId: 'ExpansionMetadata',
|
|
2584
|
+
manifestVersion: '1.0.0',
|
|
2585
|
+
runtimeInputs: [
|
|
2586
|
+
{ name: 'config', type: 'ExpansionMetadata', description: 'Canonical accordion and panel configuration.' },
|
|
2587
|
+
{ name: 'expansionId', type: 'string', description: 'Stable id used to derive expansion config persistence scope.' },
|
|
2588
|
+
{ name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
|
|
2589
|
+
{ name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
|
|
2590
|
+
{ name: 'strictValidation', type: 'boolean', description: 'Controls nested widget validation strictness.' },
|
|
2591
|
+
{ name: 'defaultOptions', type: 'MatExpansionPanelDefaultOptions', description: 'Instance-level Material expansion defaults.' },
|
|
2592
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
|
|
2593
|
+
],
|
|
2594
|
+
editableTargets: [
|
|
2595
|
+
{ kind: 'panel', resolver: 'panel-by-id-or-title', description: 'A panel in config.panels[].' },
|
|
2596
|
+
{ kind: 'panelHeader', resolver: 'panel-by-id-or-title', description: 'Header title, description, icon and heights for a panel.' },
|
|
2597
|
+
{ kind: 'panelContent', resolver: 'panel-content-by-id', description: 'Lazy panel content hosted through fields, widgets or action buttons.' },
|
|
2598
|
+
{ kind: 'expandedState', resolver: 'panel-by-id-or-title', description: 'Panel expanded state and default expanded selection.' },
|
|
2599
|
+
{ kind: 'disabledState', resolver: 'panel-by-id-or-title', description: 'Panel disabled state.' },
|
|
2600
|
+
{ kind: 'layout', resolver: 'expansion-layout-config', description: 'Accordion display mode, toggle position, density and visual layout.' },
|
|
2601
|
+
{ kind: 'behavior', resolver: 'expansion-behavior-config', description: 'Accordion multi-expand and hide-toggle behavior.' },
|
|
2602
|
+
],
|
|
2603
|
+
operations: [
|
|
2604
|
+
{
|
|
2605
|
+
operationId: 'panel.add',
|
|
2606
|
+
title: 'Add panel',
|
|
2607
|
+
scope: 'global',
|
|
2608
|
+
targetKind: 'panel',
|
|
2609
|
+
target: { kind: 'panel', resolver: 'panels-array', ambiguityPolicy: 'fail', required: false },
|
|
2610
|
+
inputSchema: panelItemSchema,
|
|
2611
|
+
effects: [{ kind: 'append-unique', path: 'panels[]', key: 'id' }],
|
|
2612
|
+
destructive: false,
|
|
2613
|
+
requiresConfirmation: false,
|
|
2614
|
+
validators: ['panel-id-unique', 'panel-order-deterministic', 'panel-content-valid'],
|
|
2615
|
+
affectedPaths: ['panels[]'],
|
|
2616
|
+
submissionImpact: 'config-only',
|
|
2617
|
+
preconditions: ['config-initialized'],
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
operationId: 'panel.remove',
|
|
2621
|
+
title: 'Remove panel',
|
|
2622
|
+
scope: 'layout',
|
|
2623
|
+
targetKind: 'panel',
|
|
2624
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2625
|
+
inputSchema: {
|
|
2626
|
+
type: 'object',
|
|
2627
|
+
properties: {
|
|
2628
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
effects: [{
|
|
2632
|
+
kind: 'compile-domain-patch',
|
|
2633
|
+
handler: 'expansion-panel-remove',
|
|
2634
|
+
handlerContract: {
|
|
2635
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2636
|
+
writes: ['panels[]', 'panels[].expanded'],
|
|
2637
|
+
identityKeys: ['panels[].id'],
|
|
2638
|
+
inputSchema: {
|
|
2639
|
+
type: 'object',
|
|
2640
|
+
properties: {
|
|
2641
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2642
|
+
},
|
|
2643
|
+
},
|
|
2644
|
+
failureModes: ['panel-not-found', 'replacement-panel-not-found', 'content-removal-not-confirmed', 'single-expand-conflict'],
|
|
2645
|
+
description: 'Removes the target panel by stable id and applies replacement expanded state when the removed panel was expanded.',
|
|
2646
|
+
},
|
|
2647
|
+
}],
|
|
2648
|
+
destructive: true,
|
|
2649
|
+
requiresConfirmation: true,
|
|
2650
|
+
validators: ['panel-exists', 'default-expanded-removal-safe', 'panel-content-removal-confirmed'],
|
|
2651
|
+
affectedPaths: ['panels[]', 'panels[].expanded'],
|
|
2652
|
+
submissionImpact: 'config-only',
|
|
2653
|
+
preconditions: ['config-initialized', 'target-panel-exists', 'confirmation-collected'],
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
operationId: 'panel.title.set',
|
|
2657
|
+
title: 'Set panel title',
|
|
2658
|
+
scope: 'layout',
|
|
2659
|
+
targetKind: 'panelHeader',
|
|
2660
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2661
|
+
inputSchema: { type: 'object', required: ['title'], properties: { title: { type: 'string' } } },
|
|
2662
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2663
|
+
destructive: false,
|
|
2664
|
+
requiresConfirmation: false,
|
|
2665
|
+
validators: ['panel-exists', 'panel-title-valid'],
|
|
2666
|
+
affectedPaths: ['panels[].title'],
|
|
2667
|
+
submissionImpact: 'config-only',
|
|
2668
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2669
|
+
},
|
|
2670
|
+
{
|
|
2671
|
+
operationId: 'panel.description.set',
|
|
2672
|
+
title: 'Set panel description',
|
|
2673
|
+
scope: 'layout',
|
|
2674
|
+
targetKind: 'panelHeader',
|
|
2675
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2676
|
+
inputSchema: { type: 'object', required: ['description'], properties: { description: { type: 'string' } } },
|
|
2677
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2678
|
+
destructive: false,
|
|
2679
|
+
requiresConfirmation: false,
|
|
2680
|
+
validators: ['panel-exists', 'panel-description-valid'],
|
|
2681
|
+
affectedPaths: ['panels[].description'],
|
|
2682
|
+
submissionImpact: 'config-only',
|
|
2683
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
operationId: 'panel.icon.set',
|
|
2687
|
+
title: 'Set panel icon',
|
|
2688
|
+
scope: 'layout',
|
|
2689
|
+
targetKind: 'panelHeader',
|
|
2690
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2691
|
+
inputSchema: { type: 'object', required: ['icon'], properties: { icon: { type: 'string' } } },
|
|
2692
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2693
|
+
destructive: false,
|
|
2694
|
+
requiresConfirmation: false,
|
|
2695
|
+
validators: ['panel-exists', 'panel-icon-valid', 'editor-runtime-round-trip'],
|
|
2696
|
+
affectedPaths: ['panels[].icon'],
|
|
2697
|
+
submissionImpact: 'config-only',
|
|
2698
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
operationId: 'panel.order.set',
|
|
2702
|
+
title: 'Reorder panels',
|
|
2703
|
+
scope: 'layout',
|
|
2704
|
+
targetKind: 'panel',
|
|
2705
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2706
|
+
inputSchema: { type: 'object', required: ['beforePanelId'], properties: { beforePanelId: { type: 'string' } } },
|
|
2707
|
+
effects: [{ kind: 'reorder-by-key', path: 'panels[]', key: 'id' }],
|
|
2708
|
+
destructive: false,
|
|
2709
|
+
requiresConfirmation: false,
|
|
2710
|
+
validators: ['panel-exists', 'panel-order-deterministic'],
|
|
2711
|
+
affectedPaths: ['panels[]'],
|
|
2712
|
+
submissionImpact: 'config-only',
|
|
2713
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2714
|
+
},
|
|
2715
|
+
{
|
|
2716
|
+
operationId: 'panel.disabled.set',
|
|
2717
|
+
title: 'Set disabled state',
|
|
2718
|
+
scope: 'interaction',
|
|
2719
|
+
targetKind: 'disabledState',
|
|
2720
|
+
target: { kind: 'disabledState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2721
|
+
inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
|
|
2722
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2723
|
+
destructive: false,
|
|
2724
|
+
requiresConfirmation: false,
|
|
2725
|
+
validators: ['panel-exists', 'disabled-expanded-compatible'],
|
|
2726
|
+
affectedPaths: ['panels[].disabled', 'panels[].expanded'],
|
|
2727
|
+
submissionImpact: 'config-only',
|
|
2728
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
operationId: 'behavior.multiExpand.set',
|
|
2732
|
+
title: 'Set multi-expand behavior',
|
|
2733
|
+
scope: 'interaction',
|
|
2734
|
+
targetKind: 'behavior',
|
|
2735
|
+
target: { kind: 'behavior', resolver: 'expansion-behavior-config', ambiguityPolicy: 'fail', required: true },
|
|
2736
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2737
|
+
effects: [{
|
|
2738
|
+
kind: 'compile-domain-patch',
|
|
2739
|
+
handler: 'expansion-multi-expand-set',
|
|
2740
|
+
handlerContract: {
|
|
2741
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2742
|
+
writes: ['accordion.multi', 'panels[].expanded'],
|
|
2743
|
+
identityKeys: ['panels[].id'],
|
|
2744
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2745
|
+
failureModes: ['multiple-expanded-panels-conflict', 'panel-id-missing'],
|
|
2746
|
+
description: 'Sets accordion.multi and collapses competing expanded panels when switching to single-expand behavior.',
|
|
2747
|
+
},
|
|
2748
|
+
}],
|
|
2749
|
+
destructive: false,
|
|
2750
|
+
requiresConfirmation: false,
|
|
2751
|
+
validators: ['multi-expand-default-compatible', 'accordion-values-valid', 'editor-runtime-round-trip'],
|
|
2752
|
+
affectedPaths: ['accordion.multi', 'panels[].expanded'],
|
|
2753
|
+
submissionImpact: 'config-only',
|
|
2754
|
+
preconditions: ['config-initialized'],
|
|
2755
|
+
},
|
|
2756
|
+
{
|
|
2757
|
+
operationId: 'behavior.defaultExpanded.set',
|
|
2758
|
+
title: 'Set default expanded panel',
|
|
2759
|
+
scope: 'interaction',
|
|
2760
|
+
targetKind: 'expandedState',
|
|
2761
|
+
target: { kind: 'expandedState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2762
|
+
inputSchema: {
|
|
2763
|
+
type: 'object',
|
|
2764
|
+
required: ['expanded'],
|
|
2765
|
+
properties: {
|
|
2766
|
+
expanded: { type: 'boolean' },
|
|
2767
|
+
collapseOthers: { type: 'boolean' },
|
|
2768
|
+
},
|
|
2769
|
+
},
|
|
2770
|
+
effects: [{
|
|
2771
|
+
kind: 'compile-domain-patch',
|
|
2772
|
+
handler: 'expansion-default-expanded-upsert',
|
|
2773
|
+
handlerContract: {
|
|
2774
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded', 'panels[].disabled'],
|
|
2775
|
+
writes: ['panels[].expanded'],
|
|
2776
|
+
identityKeys: ['panels[].id'],
|
|
2777
|
+
inputSchema: {
|
|
2778
|
+
type: 'object',
|
|
2779
|
+
required: ['panelId', 'expanded'],
|
|
2780
|
+
properties: {
|
|
2781
|
+
panelId: { type: 'string' },
|
|
2782
|
+
expanded: { type: 'boolean' },
|
|
2783
|
+
collapseOthers: { type: 'boolean' },
|
|
2784
|
+
},
|
|
2785
|
+
},
|
|
2786
|
+
failureModes: ['panel-not-found', 'panel-disabled', 'single-expand-conflict'],
|
|
2787
|
+
description: 'Sets a panel expanded state and collapses competing panels when accordion.multi is false.',
|
|
2788
|
+
},
|
|
2789
|
+
}],
|
|
2790
|
+
validators: ['panel-exists', 'default-expanded-panel-exists', 'multi-expand-default-compatible', 'disabled-expanded-compatible'],
|
|
2791
|
+
destructive: false,
|
|
2792
|
+
requiresConfirmation: false,
|
|
2793
|
+
affectedPaths: ['panels[].expanded', 'accordion.multi'],
|
|
2794
|
+
submissionImpact: 'config-only',
|
|
2795
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
operationId: 'panel.content.set',
|
|
2799
|
+
title: 'Set panel content',
|
|
2800
|
+
scope: 'layout',
|
|
2801
|
+
targetKind: 'panelContent',
|
|
2802
|
+
target: { kind: 'panelContent', resolver: 'panel-content-by-id', ambiguityPolicy: 'fail', required: true },
|
|
2803
|
+
inputSchema: panelPatchSchema,
|
|
2804
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2805
|
+
destructive: false,
|
|
2806
|
+
requiresConfirmation: false,
|
|
2807
|
+
validators: ['panel-exists', 'panel-content-valid', 'lazy-content-compatible', 'nested-widget-contract-delegated'],
|
|
2808
|
+
affectedPaths: ['panels[].content', 'panels[].widgets', 'panels[].actionButtons'],
|
|
2809
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
2810
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2811
|
+
},
|
|
2812
|
+
],
|
|
2813
|
+
validators: [
|
|
2814
|
+
{ validatorId: 'panel-id-unique', level: 'error', code: 'PEXP001', description: 'Panel ids must be unique and stable within config.panels[].' },
|
|
2815
|
+
{ validatorId: 'panel-exists', level: 'error', code: 'PEXP002', description: 'Target panel must exist before applying the operation.' },
|
|
2816
|
+
{ validatorId: 'panel-order-deterministic', level: 'error', code: 'PEXP003', description: 'Panel ordering must use stable ids, not transient array index as identity.' },
|
|
2817
|
+
{ validatorId: 'panel-title-valid', level: 'error', code: 'PEXP004', description: 'Panel title must be a non-empty text value after localization/domain projection.' },
|
|
2818
|
+
{ validatorId: 'panel-description-valid', level: 'warning', code: 'PEXP005', description: 'Panel description should remain plain header-support text and not replace panel content.' },
|
|
2819
|
+
{ validatorId: 'panel-icon-valid', level: 'warning', code: 'PEXP006', description: 'Panel icon metadata must remain compatible with PraxisIconDirective and editor round-trip.' },
|
|
2820
|
+
{ validatorId: 'panel-content-valid', level: 'error', code: 'PEXP007', description: 'Panel content must remain valid FieldMetadata[], WidgetDefinition[] or action button metadata.' },
|
|
2821
|
+
{ validatorId: 'panel-content-removal-confirmed', level: 'error', code: 'PEXP008', description: 'Removing a panel with fields, widgets or action buttons is destructive and requires confirmation.' },
|
|
2822
|
+
{ validatorId: 'default-expanded-panel-exists', level: 'error', code: 'PEXP009', description: 'Default expanded state must reference an existing panel id.' },
|
|
2823
|
+
{ validatorId: 'default-expanded-removal-safe', level: 'error', code: 'PEXP010', description: 'Removing an expanded panel requires deterministic replacement state or explicit confirmation.' },
|
|
2824
|
+
{ 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.' },
|
|
2825
|
+
{ validatorId: 'disabled-expanded-compatible', level: 'warning', code: 'PEXP012', description: 'A disabled panel should not be the only expanded/default focus target without explicit intent.' },
|
|
2826
|
+
{ validatorId: 'accordion-values-valid', level: 'error', code: 'PEXP013', description: 'Accordion behavior values must match ExpansionMetadata and Angular Material expansion bindings.' },
|
|
2827
|
+
{ validatorId: 'lazy-content-compatible', level: 'info', code: 'PEXP015', description: 'Panel content remains lazy through matExpansionPanelContent and should not require eager child runtime state.' },
|
|
2828
|
+
{ validatorId: 'nested-widget-contract-delegated', level: 'info', code: 'PEXP016', description: 'Nested widget content remains governed by child component contracts and component-port nestedPath semantics.' },
|
|
2829
|
+
{ 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.' },
|
|
2830
|
+
],
|
|
2831
|
+
roundTripRequirements: [
|
|
2832
|
+
'Operations must preserve stable panel ids; array index may be used only as a resolver fallback, never as canonical identity.',
|
|
2833
|
+
'Settings Panel editor, runtime persistence and registry projection must round-trip ExpansionMetadata without losing panel ids, order, icons or expanded state.',
|
|
2834
|
+
'When accordion.multi is false, authoring must collapse competing panels or fail validation instead of producing multiple default-expanded panels.',
|
|
2835
|
+
'Lazy panel content remains represented by panels[].content, panels[].widgets and panels[].actionButtons; authoring cannot require eager child component instances.',
|
|
2836
|
+
'Nested widgets remain delegated through WidgetDefinition and component-port nestedPath semantics instead of being redefined by the expansion contract.',
|
|
2837
|
+
],
|
|
2838
|
+
examples: [
|
|
2839
|
+
{ id: 'add-summary-panel', request: 'Add a summary panel before the audit panel.', operationId: 'panel.add', params: { id: 'summary', title: 'Summary' }, isPositive: true },
|
|
2840
|
+
{ id: 'rename-details-panel', request: 'Rename details to Account details.', operationId: 'panel.title.set', target: 'details', params: { title: 'Account details' }, isPositive: true },
|
|
2841
|
+
{ id: 'describe-audit-panel', request: 'Set audit panel description to Recent changes.', operationId: 'panel.description.set', target: 'audit', params: { description: 'Recent changes' }, isPositive: true },
|
|
2842
|
+
{ id: 'set-panel-icon', request: 'Use the info icon on the summary panel.', operationId: 'panel.icon.set', target: 'summary', params: { icon: 'info' }, isPositive: true },
|
|
2843
|
+
{ 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 },
|
|
2844
|
+
{ id: 'enable-multi-expand', request: 'Allow multiple panels to stay open.', operationId: 'behavior.multiExpand.set', params: { multi: true }, isPositive: true },
|
|
2845
|
+
{ id: 'disable-archive-panel', request: 'Disable the archive panel.', operationId: 'panel.disabled.set', target: 'archive', params: { disabled: true }, isPositive: true },
|
|
2846
|
+
{ id: 'reject-missing-default-expanded', request: 'Open the missing panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'missing', params: { expanded: true }, isPositive: false },
|
|
2847
|
+
{ id: 'reject-duplicate-panel-id', request: 'Add another panel with id summary.', operationId: 'panel.add', params: { id: 'summary', title: 'Duplicate summary' }, isPositive: false },
|
|
2848
|
+
{ id: 'confirm-remove-content-panel', request: 'Remove the details panel that contains widgets.', operationId: 'panel.remove', target: 'details', params: { replacementExpandedPanelId: 'summary' }, isPositive: true },
|
|
2849
|
+
],
|
|
2850
|
+
};
|
|
2851
|
+
|
|
1694
2852
|
/*
|
|
1695
2853
|
* Public API Surface of praxis-expansion
|
|
1696
2854
|
*/
|
|
@@ -1699,4 +2857,4 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1699
2857
|
* Generated bundle index. Do not edit.
|
|
1700
2858
|
*/
|
|
1701
2859
|
|
|
1702
|
-
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|
|
2860
|
+
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, PraxisExpansionWidgetConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|