@praxisui/expansion 8.0.0-beta.9 → 8.0.0-beta.90
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 +1374 -153
- package/package.json +16 -11
- package/{index.d.ts → types/praxisui-expansion.d.ts} +81 -6
|
@@ -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,286 @@ 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.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.optionalJsonObject(request.action?.contextHints));
|
|
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
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
304
|
+
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
305
|
+
clarificationQuestions: this.toClarificationQuestions(response),
|
|
306
|
+
quickReplies: this.toQuickReplies(response),
|
|
307
|
+
canApply: false,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (response.type === 'info') {
|
|
311
|
+
const message = response.message || response.explanation || 'Informacao gerada.';
|
|
312
|
+
return { state: 'success', phase: 'summarize', sessionId: response.sessionId ?? request.sessionId, assistantMessage: message, statusText: message, canApply: false };
|
|
313
|
+
}
|
|
314
|
+
if (response.type === 'error') {
|
|
315
|
+
const message = response.message || 'Falha ao gerar alteracao de expansion.';
|
|
316
|
+
return {
|
|
317
|
+
state: 'error',
|
|
318
|
+
phase: 'capture',
|
|
319
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
320
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
321
|
+
assistantMessage: message,
|
|
322
|
+
errorText: message,
|
|
323
|
+
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
327
|
+
return {
|
|
328
|
+
state: 'error',
|
|
329
|
+
phase: 'review',
|
|
330
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
331
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
332
|
+
assistantMessage: 'O expansion rejeitou patch livre. Gere um componentEditPlan validado pelo PRAXIS_EXPANSION_AUTHORING_MANIFEST antes de propor alteracao local.',
|
|
333
|
+
errorText: 'Patch livre de expansion rejeitado.',
|
|
334
|
+
canApply: false,
|
|
335
|
+
pendingPatch: null,
|
|
336
|
+
diagnostics: {
|
|
337
|
+
warnings: [
|
|
338
|
+
'free-expansion-patch-rejected',
|
|
339
|
+
'Use componentEditPlan validado contra PRAXIS_EXPANSION_AUTHORING_MANIFEST.',
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
state: 'success',
|
|
346
|
+
phase: 'summarize',
|
|
347
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
348
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
349
|
+
assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
350
|
+
statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
351
|
+
canApply: false,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
compileAdapterResponse(response) {
|
|
355
|
+
const compiled = this.adapter.compileAiResponse?.(response);
|
|
356
|
+
if (!compiled)
|
|
357
|
+
return response;
|
|
358
|
+
if (compiled.type === 'error') {
|
|
359
|
+
return {
|
|
360
|
+
type: 'error',
|
|
361
|
+
message: compiled.message || 'O componentEditPlan do expansion nao passou na validacao de capacidades.',
|
|
362
|
+
warnings: compiled.warnings,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const warnings = [...(response.warnings ?? []), ...(compiled.warnings ?? [])];
|
|
366
|
+
return { ...response, ...compiled, patch: compiled.patch, warnings: warnings.length ? warnings : undefined };
|
|
367
|
+
}
|
|
368
|
+
toChatMessages(messages, prompt) {
|
|
369
|
+
const supported = (messages ?? [])
|
|
370
|
+
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
371
|
+
.map((message) => ({ role: message.role, content: message.text }))
|
|
372
|
+
.filter((message) => message.content.trim().length > 0);
|
|
373
|
+
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
374
|
+
}
|
|
375
|
+
toClarificationQuestions(response) {
|
|
376
|
+
const labels = response.questions?.length
|
|
377
|
+
? response.questions
|
|
378
|
+
: response.message ? [response.message] : ['Qual ajuste voce quer aplicar nos paineis?'];
|
|
379
|
+
const options = this.toQuickReplies(response).map((reply) => ({
|
|
380
|
+
id: reply.id,
|
|
381
|
+
label: reply.label,
|
|
382
|
+
value: reply.prompt,
|
|
383
|
+
description: reply.description ?? undefined,
|
|
384
|
+
contextHints: reply.contextHints ? { ...reply.contextHints } : undefined,
|
|
385
|
+
}));
|
|
386
|
+
return labels.map((label, index) => ({
|
|
387
|
+
id: `expansion-clarification-${index + 1}`,
|
|
388
|
+
type: options.length ? 'single-choice' : 'text',
|
|
389
|
+
label,
|
|
390
|
+
allowCustom: true,
|
|
391
|
+
options,
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
toQuickReplies(response) {
|
|
395
|
+
const payloads = response.optionPayloads ?? [];
|
|
396
|
+
if (payloads.length) {
|
|
397
|
+
return payloads.map((option, index) => {
|
|
398
|
+
const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
399
|
+
const prompt = option.value?.trim() || option.example?.trim() || label;
|
|
400
|
+
return {
|
|
401
|
+
id: `option-${index + 1}`,
|
|
402
|
+
label,
|
|
403
|
+
prompt,
|
|
404
|
+
kind: 'clarification-option',
|
|
405
|
+
description: option.example?.trim() || undefined,
|
|
406
|
+
contextHints: this.optionalJsonObject(option.contextHints),
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return (response.options ?? [])
|
|
411
|
+
.filter((option) => !!option?.trim())
|
|
412
|
+
.map((option, index) => ({ id: `option-${index + 1}`, label: option.trim(), prompt: option.trim(), kind: 'clarification-option' }));
|
|
413
|
+
}
|
|
414
|
+
buildCurrentStateDigest(dataProfile) {
|
|
415
|
+
const panelCount = typeof dataProfile?.['panelCount'] === 'number' ? dataProfile['panelCount'] : undefined;
|
|
416
|
+
return panelCount !== undefined ? { rowCount: panelCount } : {};
|
|
417
|
+
}
|
|
418
|
+
shouldRouteToGovernedDecision(prompt, contextHints) {
|
|
419
|
+
return shouldRoutePromptToGovernedDecision(prompt, contextHints);
|
|
420
|
+
}
|
|
421
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
422
|
+
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.';
|
|
423
|
+
return {
|
|
424
|
+
state: 'clarification',
|
|
425
|
+
phase: 'clarify',
|
|
426
|
+
sessionId: request.sessionId,
|
|
427
|
+
assistantMessage: message,
|
|
428
|
+
statusText: 'Handoff governado necessario.',
|
|
429
|
+
canApply: false,
|
|
430
|
+
quickReplies: [
|
|
431
|
+
{
|
|
432
|
+
id: 'shared-rule-handoff',
|
|
433
|
+
label: 'Continuar como regra governada',
|
|
434
|
+
prompt,
|
|
435
|
+
kind: 'shared-rule-handoff',
|
|
436
|
+
description: 'Criar intake de domain-rules em vez de aplicar patch local no expansion.',
|
|
437
|
+
icon: 'rule',
|
|
438
|
+
tone: 'warning',
|
|
439
|
+
contextHints: {
|
|
440
|
+
flowId: 'shared_rule_authoring',
|
|
441
|
+
source: 'praxis-expansion',
|
|
442
|
+
recommendedAction: 'domain-rules/intake',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
clarificationQuestions: [
|
|
447
|
+
{
|
|
448
|
+
id: 'expansion-governed-rule-confirmation',
|
|
449
|
+
type: 'confirm',
|
|
450
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
451
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
452
|
+
required: true,
|
|
453
|
+
options: [
|
|
454
|
+
{
|
|
455
|
+
id: 'shared-rule-handoff',
|
|
456
|
+
label: 'Sim, continuar governado',
|
|
457
|
+
value: prompt,
|
|
458
|
+
description: 'Nao aplicar como patch local do expansion.',
|
|
459
|
+
contextHints: { flowId: 'shared_rule_authoring', source: 'praxis-expansion' },
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
diagnostics: {
|
|
465
|
+
governedDecisionHandoff: {
|
|
466
|
+
flowId: 'shared_rule_authoring',
|
|
467
|
+
sourcePrompt: prompt,
|
|
468
|
+
sourceComponent: 'praxis-expansion',
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
optionalJsonObject(value) {
|
|
474
|
+
if (value === undefined || value === null)
|
|
475
|
+
return undefined;
|
|
476
|
+
const object = this.toAiJsonObject(value);
|
|
477
|
+
return Object.keys(object).length ? object : undefined;
|
|
478
|
+
}
|
|
479
|
+
mergeJsonObjects(base, overlay) {
|
|
480
|
+
if (!base)
|
|
481
|
+
return overlay;
|
|
482
|
+
if (!overlay)
|
|
483
|
+
return base;
|
|
484
|
+
return {
|
|
485
|
+
...base,
|
|
486
|
+
...overlay,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
toAiJsonObject(value) {
|
|
490
|
+
const record = this.toRecord(value);
|
|
491
|
+
if (!record)
|
|
492
|
+
return {};
|
|
493
|
+
try {
|
|
494
|
+
return JSON.parse(JSON.stringify(record));
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return {};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
toRecord(value) {
|
|
501
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
502
|
+
? value
|
|
503
|
+
: null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
187
507
|
class PraxisExpansion {
|
|
188
508
|
config;
|
|
189
509
|
expansionId;
|
|
@@ -211,9 +531,35 @@ class PraxisExpansion {
|
|
|
211
531
|
catch {
|
|
212
532
|
return undefined;
|
|
213
533
|
} })();
|
|
534
|
+
aiApi = inject(AiBackendApiService);
|
|
535
|
+
assistantSessions = inject(PraxisAssistantSessionRegistryService);
|
|
536
|
+
aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
|
|
537
|
+
aiAssistantSessionEffect = effect(() => {
|
|
538
|
+
const session = this.assistantSessions.activeSession();
|
|
539
|
+
if (!session || session.id !== this.resolveAiAssistantSessionId())
|
|
540
|
+
return;
|
|
541
|
+
if (!this.aiAssistantOpen) {
|
|
542
|
+
this.openAiAssistantFromSession(session);
|
|
543
|
+
}
|
|
544
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : /* istanbul ignore next */ []));
|
|
214
545
|
warnedMissingId = false;
|
|
215
546
|
panelForms = new Map();
|
|
216
547
|
aiAdapter = new ExpansionAiAdapter(this);
|
|
548
|
+
aiAssistantOpen = false;
|
|
549
|
+
aiAssistantPrompt = '';
|
|
550
|
+
aiAssistantViewState = null;
|
|
551
|
+
aiAssistantLayout = createPraxisAssistantViewportLayout();
|
|
552
|
+
aiAssistantLabels = {
|
|
553
|
+
title: 'Copiloto semantico Praxis',
|
|
554
|
+
subtitle: 'Converse, revise e governe ajustes dos paineis.',
|
|
555
|
+
prompt: 'Mensagem',
|
|
556
|
+
promptPlaceholder: 'Descreva o ajuste que voce precisa nos paineis.',
|
|
557
|
+
emptyConversation: 'Diga o que voce quer alterar no expansion.',
|
|
558
|
+
submit: 'Interpretar pedido',
|
|
559
|
+
apply: 'Aplicar ajuste',
|
|
560
|
+
};
|
|
561
|
+
aiAssistantController = null;
|
|
562
|
+
aiAssistantStateSubscription = null;
|
|
217
563
|
accordionRef;
|
|
218
564
|
panels;
|
|
219
565
|
injectedDefaults = inject(MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, { optional: true });
|
|
@@ -237,6 +583,10 @@ class PraxisExpansion {
|
|
|
237
583
|
this.persistConfig(this.config);
|
|
238
584
|
}
|
|
239
585
|
}
|
|
586
|
+
ngOnDestroy() {
|
|
587
|
+
this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
|
|
588
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
589
|
+
}
|
|
240
590
|
styleCss() {
|
|
241
591
|
const t = this.config?.appearance?.tokens;
|
|
242
592
|
const appearance = this.config?.appearance;
|
|
@@ -476,6 +826,315 @@ class PraxisExpansion {
|
|
|
476
826
|
}
|
|
477
827
|
this.cdr.markForCheck();
|
|
478
828
|
}
|
|
829
|
+
openAiAssistant() {
|
|
830
|
+
this.initializeAiAssistantController();
|
|
831
|
+
this.aiAssistantOpen = true;
|
|
832
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
833
|
+
this.syncAiAssistantSession('active');
|
|
834
|
+
this.cdr.markForCheck();
|
|
835
|
+
}
|
|
836
|
+
openAiAssistantFromSession(session) {
|
|
837
|
+
if (session.id !== this.resolveAiAssistantSessionId())
|
|
838
|
+
return;
|
|
839
|
+
this.initializeAiAssistantController();
|
|
840
|
+
this.aiAssistantOpen = true;
|
|
841
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
842
|
+
this.syncAiAssistantSession('active');
|
|
843
|
+
this.cdr.markForCheck();
|
|
844
|
+
}
|
|
845
|
+
closeAiAssistant() {
|
|
846
|
+
this.aiAssistantOpen = false;
|
|
847
|
+
this.syncAiAssistantSession('minimized');
|
|
848
|
+
this.cdr.markForCheck();
|
|
849
|
+
}
|
|
850
|
+
onAiAssistantPromptChange(prompt) {
|
|
851
|
+
this.aiAssistantPrompt = prompt;
|
|
852
|
+
this.syncAiAssistantSession();
|
|
853
|
+
}
|
|
854
|
+
onAiAssistantSubmit(prompt) {
|
|
855
|
+
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
856
|
+
this.aiAssistantPrompt = '';
|
|
857
|
+
this.aiAssistantViewState = state;
|
|
858
|
+
this.syncAiAssistantSession();
|
|
859
|
+
this.cdr.markForCheck();
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
onAiAssistantApply() {
|
|
863
|
+
this.aiAssistantController?.apply().subscribe((state) => {
|
|
864
|
+
this.aiAssistantViewState = state;
|
|
865
|
+
this.syncAiAssistantSession();
|
|
866
|
+
this.cdr.markForCheck();
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
onAiAssistantRetry() {
|
|
870
|
+
this.aiAssistantController?.retry().subscribe((state) => {
|
|
871
|
+
this.aiAssistantViewState = state;
|
|
872
|
+
this.syncAiAssistantSession();
|
|
873
|
+
this.cdr.markForCheck();
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
onAiAssistantCancel() {
|
|
877
|
+
this.aiAssistantController?.cancel().subscribe((state) => {
|
|
878
|
+
this.aiAssistantPrompt = '';
|
|
879
|
+
this.aiAssistantViewState = state;
|
|
880
|
+
this.syncAiAssistantSession();
|
|
881
|
+
this.cdr.markForCheck();
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
onAiAssistantQuickReply(reply) {
|
|
885
|
+
const controller = this.aiAssistantController;
|
|
886
|
+
if (!controller)
|
|
887
|
+
return;
|
|
888
|
+
const state = controller.snapshot();
|
|
889
|
+
const contextHints = reply.contextHints ? { ...reply.contextHints } : undefined;
|
|
890
|
+
const next$ = state.state === 'clarification'
|
|
891
|
+
? controller.answerClarification({
|
|
892
|
+
id: reply.id,
|
|
893
|
+
label: reply.label,
|
|
894
|
+
value: reply.prompt,
|
|
895
|
+
description: reply.description ?? undefined,
|
|
896
|
+
contextHints,
|
|
897
|
+
})
|
|
898
|
+
: controller.submitPrompt(reply.prompt, {
|
|
899
|
+
kind: reply.kind || 'quick-reply',
|
|
900
|
+
id: reply.id,
|
|
901
|
+
value: reply.prompt,
|
|
902
|
+
displayPrompt: reply.label,
|
|
903
|
+
contextHints,
|
|
904
|
+
});
|
|
905
|
+
next$.subscribe((nextState) => {
|
|
906
|
+
this.aiAssistantPrompt = '';
|
|
907
|
+
this.aiAssistantViewState = nextState;
|
|
908
|
+
this.syncAiAssistantSession();
|
|
909
|
+
this.cdr.markForCheck();
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
onAiAssistantEditMessage(message) {
|
|
913
|
+
this.aiAssistantPrompt = message.text;
|
|
914
|
+
this.cdr.markForCheck();
|
|
915
|
+
}
|
|
916
|
+
onAiAssistantResendMessage(message) {
|
|
917
|
+
this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
|
|
918
|
+
this.aiAssistantPrompt = '';
|
|
919
|
+
this.aiAssistantViewState = state;
|
|
920
|
+
this.syncAiAssistantSession();
|
|
921
|
+
this.cdr.markForCheck();
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
onAiAssistantLayoutChange(layout) {
|
|
925
|
+
this.aiAssistantLayout = layout;
|
|
926
|
+
}
|
|
927
|
+
buildAiAssistantContextItems() {
|
|
928
|
+
const panels = this.config?.panels ?? [];
|
|
929
|
+
const items = [
|
|
930
|
+
{
|
|
931
|
+
id: 'component',
|
|
932
|
+
label: 'Componente',
|
|
933
|
+
value: 'Expansion',
|
|
934
|
+
kind: 'component',
|
|
935
|
+
icon: 'unfold_more',
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: 'expansion-id',
|
|
939
|
+
label: 'Expansion',
|
|
940
|
+
value: this.safeAiAssistantExpansionId(),
|
|
941
|
+
kind: 'custom',
|
|
942
|
+
icon: 'tag',
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
id: 'panels',
|
|
946
|
+
label: 'Paineis',
|
|
947
|
+
value: String(panels.length),
|
|
948
|
+
kind: 'custom',
|
|
949
|
+
icon: 'view_agenda',
|
|
950
|
+
},
|
|
951
|
+
];
|
|
952
|
+
const expanded = panels.filter((panel) => panel.expanded).length;
|
|
953
|
+
if (expanded > 0) {
|
|
954
|
+
items.push({
|
|
955
|
+
id: 'expanded-panels',
|
|
956
|
+
label: 'Expandidos',
|
|
957
|
+
value: String(expanded),
|
|
958
|
+
kind: 'custom',
|
|
959
|
+
icon: 'expand_less',
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return items;
|
|
963
|
+
}
|
|
964
|
+
initializeAiAssistantController() {
|
|
965
|
+
if (this.aiAssistantController)
|
|
966
|
+
return;
|
|
967
|
+
const flow = new ExpansionAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
|
|
968
|
+
const controller = this.aiTurnOrchestrator.createController(flow, {
|
|
969
|
+
componentId: this.aiAdapter.componentId || 'praxis-expansion',
|
|
970
|
+
componentType: this.aiAdapter.componentType || 'expansion',
|
|
971
|
+
contextItems: this.buildAiAssistantContextItems(),
|
|
972
|
+
});
|
|
973
|
+
this.aiAssistantController = controller;
|
|
974
|
+
this.aiAssistantViewState = controller.snapshot();
|
|
975
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
976
|
+
this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
|
|
977
|
+
this.aiAssistantViewState = state;
|
|
978
|
+
this.syncAiAssistantSession();
|
|
979
|
+
this.cdr.markForCheck();
|
|
980
|
+
});
|
|
981
|
+
this.cdr.markForCheck();
|
|
982
|
+
}
|
|
983
|
+
buildAiAssistantContextSnapshot() {
|
|
984
|
+
const counts = this.collectAiAssistantCounts();
|
|
985
|
+
const panelNames = this.collectAiAssistantPanelNames();
|
|
986
|
+
return {
|
|
987
|
+
identity: {
|
|
988
|
+
sessionId: this.resolveAiAssistantSessionId(),
|
|
989
|
+
ownerId: this.resolveAiAssistantOwnerId(),
|
|
990
|
+
ownerType: 'expansion',
|
|
991
|
+
componentId: 'praxis-expansion',
|
|
992
|
+
componentType: 'expansion',
|
|
993
|
+
routeKey: this.resolveAiAssistantRouteKey(),
|
|
994
|
+
},
|
|
995
|
+
target: {
|
|
996
|
+
kind: 'component',
|
|
997
|
+
id: this.resolveAiAssistantOwnerId(),
|
|
998
|
+
label: this.safeAiAssistantExpansionId(),
|
|
999
|
+
metadata: {
|
|
1000
|
+
expansionId: this.safeAiAssistantExpansionId(),
|
|
1001
|
+
hasCustomization: !!this.enableCustomization,
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
contextItems: this.buildAiAssistantContextItems().map((item) => ({
|
|
1005
|
+
id: item.id,
|
|
1006
|
+
label: item.label,
|
|
1007
|
+
value: item.value || '',
|
|
1008
|
+
kind: item.kind,
|
|
1009
|
+
})),
|
|
1010
|
+
mode: 'agentic-authoring',
|
|
1011
|
+
authoringManifestRef: {
|
|
1012
|
+
componentId: 'praxis-expansion',
|
|
1013
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
1014
|
+
},
|
|
1015
|
+
schemaFields: panelNames.length ? panelNames : undefined,
|
|
1016
|
+
dataProfileDigest: {
|
|
1017
|
+
summary: `${counts.panelCount} painel(is), ${counts.contentPanelCount} com campo(s), ${counts.widgetPanelCount} com widget(s)`,
|
|
1018
|
+
counts,
|
|
1019
|
+
},
|
|
1020
|
+
runtimeStateDigest: {
|
|
1021
|
+
summary: `Expansion ${this.config?.accordion?.multi ? 'multi' : 'single'}, ${counts.expandedCount} painel(is) expandido(s)`,
|
|
1022
|
+
fields: [
|
|
1023
|
+
'panels',
|
|
1024
|
+
'accordion',
|
|
1025
|
+
'content',
|
|
1026
|
+
'widgets',
|
|
1027
|
+
],
|
|
1028
|
+
},
|
|
1029
|
+
capabilityRefs: [
|
|
1030
|
+
{
|
|
1031
|
+
id: 'expansion.component-edit-plan',
|
|
1032
|
+
label: 'Plano de edicao de paineis',
|
|
1033
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
1034
|
+
risk: 'medium',
|
|
1035
|
+
},
|
|
1036
|
+
],
|
|
1037
|
+
governanceHints: [
|
|
1038
|
+
{
|
|
1039
|
+
kind: 'business-rule-boundary',
|
|
1040
|
+
label: 'Regras compartilhadas exigem governanca',
|
|
1041
|
+
reason: 'Politicas de acesso, validacoes reutilizaveis e compliance nao devem ser aplicadas como patch local do expansion.',
|
|
1042
|
+
risk: 'high',
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
syncAiAssistantSession(visibility = null) {
|
|
1048
|
+
if (!this.enableCustomization)
|
|
1049
|
+
return;
|
|
1050
|
+
if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
|
|
1051
|
+
return;
|
|
1052
|
+
const state = this.aiAssistantViewState;
|
|
1053
|
+
this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
|
|
1054
|
+
title: 'Copiloto semantico Praxis',
|
|
1055
|
+
summary: this.resolveAiAssistantSummary(),
|
|
1056
|
+
mode: state?.mode || 'agentic-authoring',
|
|
1057
|
+
state: state?.state || 'idle',
|
|
1058
|
+
visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
|
|
1059
|
+
badge: this.resolveAiAssistantBadge(),
|
|
1060
|
+
icon: this.resolveAiAssistantIcon(),
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
hasAiAssistantSessionState() {
|
|
1064
|
+
return !!this.aiAssistantPrompt.trim()
|
|
1065
|
+
|| !!this.aiAssistantViewState?.messages?.length
|
|
1066
|
+
|| !!this.aiAssistantViewState?.quickReplies?.length
|
|
1067
|
+
|| !!this.aiAssistantViewState?.pendingPatch
|
|
1068
|
+
|| !!this.aiAssistantViewState?.statusText?.trim()
|
|
1069
|
+
|| !!this.aiAssistantViewState?.errorText?.trim();
|
|
1070
|
+
}
|
|
1071
|
+
resolveAiAssistantSessionId() {
|
|
1072
|
+
return `expansion:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
|
|
1073
|
+
}
|
|
1074
|
+
resolveAiAssistantOwnerId() {
|
|
1075
|
+
return (this.componentInstanceId || this.safeAiAssistantExpansionId() || 'expansion').trim() || 'expansion';
|
|
1076
|
+
}
|
|
1077
|
+
safeAiAssistantExpansionId() {
|
|
1078
|
+
return String(this.expansionId || '').trim();
|
|
1079
|
+
}
|
|
1080
|
+
resolveAiAssistantRouteKey() {
|
|
1081
|
+
const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
|
|
1082
|
+
return routePath || 'local';
|
|
1083
|
+
}
|
|
1084
|
+
resolveAiAssistantSummary() {
|
|
1085
|
+
const status = this.aiAssistantViewState?.statusText?.trim();
|
|
1086
|
+
if (status)
|
|
1087
|
+
return status;
|
|
1088
|
+
const error = this.aiAssistantViewState?.errorText?.trim();
|
|
1089
|
+
if (error)
|
|
1090
|
+
return error;
|
|
1091
|
+
const prompt = this.aiAssistantPrompt.trim();
|
|
1092
|
+
if (prompt)
|
|
1093
|
+
return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
|
|
1094
|
+
const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
|
|
1095
|
+
.find((message) => message.role === 'assistant' || message.role === 'user');
|
|
1096
|
+
if (lastMessage?.text) {
|
|
1097
|
+
return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
|
|
1098
|
+
}
|
|
1099
|
+
return 'Assistente contextual dos paineis.';
|
|
1100
|
+
}
|
|
1101
|
+
resolveAiAssistantBadge() {
|
|
1102
|
+
const state = this.aiAssistantViewState?.state;
|
|
1103
|
+
if (state === 'error')
|
|
1104
|
+
return 'erro';
|
|
1105
|
+
if (state === 'clarification')
|
|
1106
|
+
return 'revisar';
|
|
1107
|
+
if (state === 'review')
|
|
1108
|
+
return 'preview';
|
|
1109
|
+
if (state === 'success')
|
|
1110
|
+
return 'ok';
|
|
1111
|
+
return undefined;
|
|
1112
|
+
}
|
|
1113
|
+
resolveAiAssistantIcon() {
|
|
1114
|
+
const state = this.aiAssistantViewState?.state;
|
|
1115
|
+
if (state === 'error')
|
|
1116
|
+
return 'error';
|
|
1117
|
+
if (state === 'clarification')
|
|
1118
|
+
return 'rule';
|
|
1119
|
+
if (state === 'review')
|
|
1120
|
+
return 'rate_review';
|
|
1121
|
+
return 'auto_awesome';
|
|
1122
|
+
}
|
|
1123
|
+
collectAiAssistantPanelNames() {
|
|
1124
|
+
return Array.from(new Set((this.config?.panels ?? [])
|
|
1125
|
+
.map((panel, index) => panel.id || panel.title || `panel-${index + 1}`)
|
|
1126
|
+
.filter((name) => typeof name === 'string' && !!name.trim())));
|
|
1127
|
+
}
|
|
1128
|
+
collectAiAssistantCounts() {
|
|
1129
|
+
const panels = this.config?.panels ?? [];
|
|
1130
|
+
return {
|
|
1131
|
+
panelCount: panels.length,
|
|
1132
|
+
contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
|
|
1133
|
+
widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
|
|
1134
|
+
actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
|
|
1135
|
+
expandedCount: panels.filter((panel) => !!panel.expanded).length,
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
479
1138
|
openEditor() {
|
|
480
1139
|
const key = this.storageKey() || this.expansionId || 'default';
|
|
481
1140
|
const ref = this.settings.open({
|
|
@@ -558,8 +1217,8 @@ class PraxisExpansion {
|
|
|
558
1217
|
this.warnedMissingId = true;
|
|
559
1218
|
console.warn('[PraxisExpansion] expansionId is required for config persistence.');
|
|
560
1219
|
}
|
|
561
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
562
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1220
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansion, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1221
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisExpansion, isStandalone: true, selector: "praxis-expansion", inputs: { config: "config", expansionId: "expansionId", componentInstanceId: "componentInstanceId", context: "context", strictValidation: "strictValidation", enableCustomization: "enableCustomization", defaultOptions: "defaultOptions" }, outputs: { opened: "opened", closed: "closed", expandedChange: "expandedChange", afterExpand: "afterExpand", afterCollapse: "afterCollapse", destroyed: "destroyed", widgetEvent: "widgetEvent" }, viewQueries: [{ propertyName: "accordionRef", first: true, predicate: ["accordion"], descendants: true }, { propertyName: "panels", predicate: ["panel"], descendants: true, read: MatExpansionPanel }], usesOnChanges: true, ngImport: i0, template: `
|
|
563
1222
|
<div
|
|
564
1223
|
class="praxis-expansion-root"
|
|
565
1224
|
[class.density-compact]="config?.appearance?.density === 'compact'"
|
|
@@ -567,16 +1226,59 @@ class PraxisExpansion {
|
|
|
567
1226
|
[class.density-spacious]="config?.appearance?.density === 'spacious'"
|
|
568
1227
|
[ngClass]="config?.appearance?.themeClass || ''"
|
|
569
1228
|
[attr.data-expansion-id]="expansionId || 'default'"
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
1229
|
+
>
|
|
1230
|
+
@if (config?.appearance?.customCss) {
|
|
1231
|
+
<style [innerHTML]="config?.appearance?.customCss"></style>
|
|
1232
|
+
}
|
|
1233
|
+
@if (styleCss(); as s) {
|
|
1234
|
+
<style [innerHTML]="s"></style>
|
|
1235
|
+
}
|
|
1236
|
+
|
|
574
1237
|
@if (enableCustomization) {
|
|
575
1238
|
<div class="expansion-ai-assistant">
|
|
576
|
-
<
|
|
1239
|
+
<button
|
|
1240
|
+
mat-mini-fab
|
|
1241
|
+
color="primary"
|
|
1242
|
+
type="button"
|
|
1243
|
+
class="expansion-ai-assistant-trigger"
|
|
1244
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1245
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1246
|
+
(click)="openAiAssistant()"
|
|
1247
|
+
>
|
|
1248
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1249
|
+
</button>
|
|
577
1250
|
</div>
|
|
578
1251
|
}
|
|
579
|
-
|
|
1252
|
+
|
|
1253
|
+
@if (aiAssistantOpen && aiAssistantViewState) {
|
|
1254
|
+
<praxis-ai-assistant-shell
|
|
1255
|
+
[labels]="aiAssistantLabels"
|
|
1256
|
+
[mode]="aiAssistantViewState.mode"
|
|
1257
|
+
[state]="aiAssistantViewState.state"
|
|
1258
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1259
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1260
|
+
[messages]="aiAssistantViewState.messages"
|
|
1261
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1262
|
+
[prompt]="aiAssistantPrompt"
|
|
1263
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1264
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1265
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1266
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1267
|
+
[layout]="aiAssistantLayout"
|
|
1268
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1269
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1270
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1271
|
+
(apply)="onAiAssistantApply()"
|
|
1272
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1273
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1274
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1275
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1276
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1277
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1278
|
+
(close)="closeAiAssistant()"
|
|
1279
|
+
></praxis-ai-assistant-shell>
|
|
1280
|
+
}
|
|
1281
|
+
|
|
580
1282
|
@if (hasMultiple()) {
|
|
581
1283
|
<mat-accordion
|
|
582
1284
|
#accordion
|
|
@@ -585,7 +1287,7 @@ class PraxisExpansion {
|
|
|
585
1287
|
[displayMode]="config?.accordion?.displayMode || 'default'"
|
|
586
1288
|
[togglePosition]="config?.accordion?.togglePosition || 'after'"
|
|
587
1289
|
[hideToggle]="config?.accordion?.hideToggle || false"
|
|
588
|
-
|
|
1290
|
+
>
|
|
589
1291
|
@for (p of (config?.panels || []); let i = $index; track p.id ?? i) {
|
|
590
1292
|
@let id = panelDomId(p, i);
|
|
591
1293
|
<mat-expansion-panel
|
|
@@ -600,15 +1302,20 @@ class PraxisExpansion {
|
|
|
600
1302
|
(afterExpand)="emitAfterExpand(p, i)"
|
|
601
1303
|
(afterCollapse)="emitAfterCollapse(p, i)"
|
|
602
1304
|
(destroyed)="emitDestroyed(p, i)"
|
|
603
|
-
|
|
1305
|
+
>
|
|
604
1306
|
<mat-expansion-panel-header
|
|
605
1307
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
606
1308
|
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
607
|
-
|
|
1309
|
+
>
|
|
1310
|
+
@if (p.icon) {
|
|
1311
|
+
<mat-icon [praxisIcon]="p.icon"></mat-icon>
|
|
1312
|
+
}
|
|
608
1313
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
609
|
-
|
|
1314
|
+
@if (p.description) {
|
|
1315
|
+
<mat-panel-description>{{ p.description }}</mat-panel-description>
|
|
1316
|
+
}
|
|
610
1317
|
</mat-expansion-panel-header>
|
|
611
|
-
|
|
1318
|
+
|
|
612
1319
|
<ng-template matExpansionPanelContent>
|
|
613
1320
|
@if (p.content?.length) {
|
|
614
1321
|
<ng-container
|
|
@@ -617,7 +1324,7 @@ class PraxisExpansion {
|
|
|
617
1324
|
[formGroup]="panelFormFor(p, i)"
|
|
618
1325
|
></ng-container>
|
|
619
1326
|
}
|
|
620
|
-
|
|
1327
|
+
|
|
621
1328
|
@if (p.widgets?.length) {
|
|
622
1329
|
@for (w of p.widgets; let wi = $index; track wi) {
|
|
623
1330
|
<ng-container
|
|
@@ -628,12 +1335,14 @@ class PraxisExpansion {
|
|
|
628
1335
|
></ng-container>
|
|
629
1336
|
}
|
|
630
1337
|
}
|
|
631
|
-
|
|
1338
|
+
|
|
632
1339
|
@if (p.actionButtons?.length) {
|
|
633
1340
|
<mat-action-row>
|
|
634
1341
|
@for (a of p.actionButtons; track a.label) {
|
|
635
1342
|
<button mat-stroked-button (click)="emitAction(p, i, a.action)">
|
|
636
|
-
|
|
1343
|
+
@if (a.icon) {
|
|
1344
|
+
<mat-icon [praxisIcon]="a.icon"></mat-icon>
|
|
1345
|
+
}
|
|
637
1346
|
{{ a.label }}
|
|
638
1347
|
</button>
|
|
639
1348
|
}
|
|
@@ -657,15 +1366,20 @@ class PraxisExpansion {
|
|
|
657
1366
|
(afterExpand)="emitAfterExpand(p, 0)"
|
|
658
1367
|
(afterCollapse)="emitAfterCollapse(p, 0)"
|
|
659
1368
|
(destroyed)="emitDestroyed(p, 0)"
|
|
660
|
-
|
|
1369
|
+
>
|
|
661
1370
|
<mat-expansion-panel-header
|
|
662
1371
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
663
1372
|
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
664
|
-
|
|
1373
|
+
>
|
|
1374
|
+
@if (p.icon) {
|
|
1375
|
+
<mat-icon [praxisIcon]="p.icon"></mat-icon>
|
|
1376
|
+
}
|
|
665
1377
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
666
|
-
|
|
1378
|
+
@if (p.description) {
|
|
1379
|
+
<mat-panel-description>{{ p.description }}</mat-panel-description>
|
|
1380
|
+
}
|
|
667
1381
|
</mat-expansion-panel-header>
|
|
668
|
-
|
|
1382
|
+
|
|
669
1383
|
<ng-template matExpansionPanelContent>
|
|
670
1384
|
@if (p.content?.length) {
|
|
671
1385
|
<ng-container
|
|
@@ -674,7 +1388,7 @@ class PraxisExpansion {
|
|
|
674
1388
|
[formGroup]="panelFormFor(p, 0)"
|
|
675
1389
|
></ng-container>
|
|
676
1390
|
}
|
|
677
|
-
|
|
1391
|
+
|
|
678
1392
|
@if (p.widgets?.length) {
|
|
679
1393
|
@for (w of p.widgets; let wi = $index; track wi) {
|
|
680
1394
|
<ng-container
|
|
@@ -685,12 +1399,14 @@ class PraxisExpansion {
|
|
|
685
1399
|
></ng-container>
|
|
686
1400
|
}
|
|
687
1401
|
}
|
|
688
|
-
|
|
1402
|
+
|
|
689
1403
|
@if (p.actionButtons?.length) {
|
|
690
1404
|
<mat-action-row>
|
|
691
1405
|
@for (a of p.actionButtons; track a.label) {
|
|
692
1406
|
<button mat-stroked-button (click)="emitAction(p, 0, a.action)">
|
|
693
|
-
|
|
1407
|
+
@if (a.icon) {
|
|
1408
|
+
<mat-icon [praxisIcon]="a.icon"></mat-icon>
|
|
1409
|
+
}
|
|
694
1410
|
{{ a.label }}
|
|
695
1411
|
</button>
|
|
696
1412
|
}
|
|
@@ -701,26 +1417,29 @@ class PraxisExpansion {
|
|
|
701
1417
|
} @else {
|
|
702
1418
|
<div class="praxis-expansion-empty">
|
|
703
1419
|
<span>Nenhum painel configurado</span>
|
|
704
|
-
|
|
705
|
-
<mat-
|
|
706
|
-
|
|
1420
|
+
@if (enableCustomization) {
|
|
1421
|
+
<button mat-stroked-button color="primary" (click)="openEditor()">
|
|
1422
|
+
<mat-icon [praxisIcon]="'tune'"></mat-icon> Configurar
|
|
1423
|
+
</button>
|
|
1424
|
+
}
|
|
707
1425
|
</div>
|
|
708
1426
|
}
|
|
709
1427
|
}
|
|
710
1428
|
</div>
|
|
711
1429
|
<!-- Edit button -->
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1430
|
+
@if (enableCustomization) {
|
|
1431
|
+
<button
|
|
1432
|
+
mat-fab
|
|
1433
|
+
class="edit-fab"
|
|
1434
|
+
aria-label="Editar painéis"
|
|
1435
|
+
(click)="openEditor()"
|
|
1436
|
+
>
|
|
1437
|
+
<mat-icon fontIcon="edit"></mat-icon>
|
|
1438
|
+
</button>
|
|
1439
|
+
}
|
|
1440
|
+
`, 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: "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", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
722
1441
|
}
|
|
723
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1442
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansion, decorators: [{
|
|
724
1443
|
type: Component,
|
|
725
1444
|
args: [{ selector: 'praxis-expansion', standalone: true, imports: [
|
|
726
1445
|
CommonModule,
|
|
@@ -730,7 +1449,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
730
1449
|
DynamicFieldLoaderDirective,
|
|
731
1450
|
DynamicWidgetLoaderDirective,
|
|
732
1451
|
PraxisIconDirective,
|
|
733
|
-
|
|
1452
|
+
PraxisAiAssistantShellComponent,
|
|
734
1453
|
], template: `
|
|
735
1454
|
<div
|
|
736
1455
|
class="praxis-expansion-root"
|
|
@@ -739,16 +1458,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
739
1458
|
[class.density-spacious]="config?.appearance?.density === 'spacious'"
|
|
740
1459
|
[ngClass]="config?.appearance?.themeClass || ''"
|
|
741
1460
|
[attr.data-expansion-id]="expansionId || 'default'"
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1461
|
+
>
|
|
1462
|
+
@if (config?.appearance?.customCss) {
|
|
1463
|
+
<style [innerHTML]="config?.appearance?.customCss"></style>
|
|
1464
|
+
}
|
|
1465
|
+
@if (styleCss(); as s) {
|
|
1466
|
+
<style [innerHTML]="s"></style>
|
|
1467
|
+
}
|
|
1468
|
+
|
|
746
1469
|
@if (enableCustomization) {
|
|
747
1470
|
<div class="expansion-ai-assistant">
|
|
748
|
-
<
|
|
1471
|
+
<button
|
|
1472
|
+
mat-mini-fab
|
|
1473
|
+
color="primary"
|
|
1474
|
+
type="button"
|
|
1475
|
+
class="expansion-ai-assistant-trigger"
|
|
1476
|
+
aria-label="Abrir copiloto semantico Praxis dos paineis"
|
|
1477
|
+
data-testid="praxis-expansion-ai-assistant-trigger"
|
|
1478
|
+
(click)="openAiAssistant()"
|
|
1479
|
+
>
|
|
1480
|
+
<mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
|
|
1481
|
+
</button>
|
|
749
1482
|
</div>
|
|
750
1483
|
}
|
|
751
|
-
|
|
1484
|
+
|
|
1485
|
+
@if (aiAssistantOpen && aiAssistantViewState) {
|
|
1486
|
+
<praxis-ai-assistant-shell
|
|
1487
|
+
[labels]="aiAssistantLabels"
|
|
1488
|
+
[mode]="aiAssistantViewState.mode"
|
|
1489
|
+
[state]="aiAssistantViewState.state"
|
|
1490
|
+
[contextItems]="aiAssistantViewState.contextItems"
|
|
1491
|
+
[attachments]="aiAssistantViewState.attachments"
|
|
1492
|
+
[messages]="aiAssistantViewState.messages"
|
|
1493
|
+
[quickReplies]="aiAssistantViewState.quickReplies"
|
|
1494
|
+
[prompt]="aiAssistantPrompt"
|
|
1495
|
+
[statusText]="aiAssistantViewState.statusText"
|
|
1496
|
+
[errorText]="aiAssistantViewState.errorText"
|
|
1497
|
+
[busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
|
|
1498
|
+
[canApply]="aiAssistantViewState.canApply"
|
|
1499
|
+
[layout]="aiAssistantLayout"
|
|
1500
|
+
testIdPrefix="praxis-expansion-ai-assistant"
|
|
1501
|
+
(promptChange)="onAiAssistantPromptChange($event)"
|
|
1502
|
+
(submitPrompt)="onAiAssistantSubmit($event)"
|
|
1503
|
+
(apply)="onAiAssistantApply()"
|
|
1504
|
+
(retryTurn)="onAiAssistantRetry()"
|
|
1505
|
+
(cancelTurn)="onAiAssistantCancel()"
|
|
1506
|
+
(quickReply)="onAiAssistantQuickReply($event)"
|
|
1507
|
+
(editMessage)="onAiAssistantEditMessage($event)"
|
|
1508
|
+
(resendMessage)="onAiAssistantResendMessage($event)"
|
|
1509
|
+
(layoutChange)="onAiAssistantLayoutChange($event)"
|
|
1510
|
+
(close)="closeAiAssistant()"
|
|
1511
|
+
></praxis-ai-assistant-shell>
|
|
1512
|
+
}
|
|
1513
|
+
|
|
752
1514
|
@if (hasMultiple()) {
|
|
753
1515
|
<mat-accordion
|
|
754
1516
|
#accordion
|
|
@@ -757,7 +1519,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
757
1519
|
[displayMode]="config?.accordion?.displayMode || 'default'"
|
|
758
1520
|
[togglePosition]="config?.accordion?.togglePosition || 'after'"
|
|
759
1521
|
[hideToggle]="config?.accordion?.hideToggle || false"
|
|
760
|
-
|
|
1522
|
+
>
|
|
761
1523
|
@for (p of (config?.panels || []); let i = $index; track p.id ?? i) {
|
|
762
1524
|
@let id = panelDomId(p, i);
|
|
763
1525
|
<mat-expansion-panel
|
|
@@ -772,15 +1534,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
772
1534
|
(afterExpand)="emitAfterExpand(p, i)"
|
|
773
1535
|
(afterCollapse)="emitAfterCollapse(p, i)"
|
|
774
1536
|
(destroyed)="emitDestroyed(p, i)"
|
|
775
|
-
|
|
1537
|
+
>
|
|
776
1538
|
<mat-expansion-panel-header
|
|
777
1539
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
778
1540
|
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
779
|
-
|
|
1541
|
+
>
|
|
1542
|
+
@if (p.icon) {
|
|
1543
|
+
<mat-icon [praxisIcon]="p.icon"></mat-icon>
|
|
1544
|
+
}
|
|
780
1545
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
781
|
-
|
|
1546
|
+
@if (p.description) {
|
|
1547
|
+
<mat-panel-description>{{ p.description }}</mat-panel-description>
|
|
1548
|
+
}
|
|
782
1549
|
</mat-expansion-panel-header>
|
|
783
|
-
|
|
1550
|
+
|
|
784
1551
|
<ng-template matExpansionPanelContent>
|
|
785
1552
|
@if (p.content?.length) {
|
|
786
1553
|
<ng-container
|
|
@@ -789,7 +1556,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
789
1556
|
[formGroup]="panelFormFor(p, i)"
|
|
790
1557
|
></ng-container>
|
|
791
1558
|
}
|
|
792
|
-
|
|
1559
|
+
|
|
793
1560
|
@if (p.widgets?.length) {
|
|
794
1561
|
@for (w of p.widgets; let wi = $index; track wi) {
|
|
795
1562
|
<ng-container
|
|
@@ -800,12 +1567,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
800
1567
|
></ng-container>
|
|
801
1568
|
}
|
|
802
1569
|
}
|
|
803
|
-
|
|
1570
|
+
|
|
804
1571
|
@if (p.actionButtons?.length) {
|
|
805
1572
|
<mat-action-row>
|
|
806
1573
|
@for (a of p.actionButtons; track a.label) {
|
|
807
1574
|
<button mat-stroked-button (click)="emitAction(p, i, a.action)">
|
|
808
|
-
|
|
1575
|
+
@if (a.icon) {
|
|
1576
|
+
<mat-icon [praxisIcon]="a.icon"></mat-icon>
|
|
1577
|
+
}
|
|
809
1578
|
{{ a.label }}
|
|
810
1579
|
</button>
|
|
811
1580
|
}
|
|
@@ -829,15 +1598,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
829
1598
|
(afterExpand)="emitAfterExpand(p, 0)"
|
|
830
1599
|
(afterCollapse)="emitAfterCollapse(p, 0)"
|
|
831
1600
|
(destroyed)="emitDestroyed(p, 0)"
|
|
832
|
-
|
|
1601
|
+
>
|
|
833
1602
|
<mat-expansion-panel-header
|
|
834
1603
|
[collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
|
|
835
1604
|
[expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
|
|
836
|
-
|
|
1605
|
+
>
|
|
1606
|
+
@if (p.icon) {
|
|
1607
|
+
<mat-icon [praxisIcon]="p.icon"></mat-icon>
|
|
1608
|
+
}
|
|
837
1609
|
<mat-panel-title>{{ p.title }}</mat-panel-title>
|
|
838
|
-
|
|
1610
|
+
@if (p.description) {
|
|
1611
|
+
<mat-panel-description>{{ p.description }}</mat-panel-description>
|
|
1612
|
+
}
|
|
839
1613
|
</mat-expansion-panel-header>
|
|
840
|
-
|
|
1614
|
+
|
|
841
1615
|
<ng-template matExpansionPanelContent>
|
|
842
1616
|
@if (p.content?.length) {
|
|
843
1617
|
<ng-container
|
|
@@ -846,7 +1620,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
846
1620
|
[formGroup]="panelFormFor(p, 0)"
|
|
847
1621
|
></ng-container>
|
|
848
1622
|
}
|
|
849
|
-
|
|
1623
|
+
|
|
850
1624
|
@if (p.widgets?.length) {
|
|
851
1625
|
@for (w of p.widgets; let wi = $index; track wi) {
|
|
852
1626
|
<ng-container
|
|
@@ -857,12 +1631,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
857
1631
|
></ng-container>
|
|
858
1632
|
}
|
|
859
1633
|
}
|
|
860
|
-
|
|
1634
|
+
|
|
861
1635
|
@if (p.actionButtons?.length) {
|
|
862
1636
|
<mat-action-row>
|
|
863
1637
|
@for (a of p.actionButtons; track a.label) {
|
|
864
1638
|
<button mat-stroked-button (click)="emitAction(p, 0, a.action)">
|
|
865
|
-
|
|
1639
|
+
@if (a.icon) {
|
|
1640
|
+
<mat-icon [praxisIcon]="a.icon"></mat-icon>
|
|
1641
|
+
}
|
|
866
1642
|
{{ a.label }}
|
|
867
1643
|
</button>
|
|
868
1644
|
}
|
|
@@ -873,24 +1649,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
873
1649
|
} @else {
|
|
874
1650
|
<div class="praxis-expansion-empty">
|
|
875
1651
|
<span>Nenhum painel configurado</span>
|
|
876
|
-
|
|
877
|
-
<mat-
|
|
878
|
-
|
|
1652
|
+
@if (enableCustomization) {
|
|
1653
|
+
<button mat-stroked-button color="primary" (click)="openEditor()">
|
|
1654
|
+
<mat-icon [praxisIcon]="'tune'"></mat-icon> Configurar
|
|
1655
|
+
</button>
|
|
1656
|
+
}
|
|
879
1657
|
</div>
|
|
880
1658
|
}
|
|
881
1659
|
}
|
|
882
1660
|
</div>
|
|
883
1661
|
<!-- Edit button -->
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1662
|
+
@if (enableCustomization) {
|
|
1663
|
+
<button
|
|
1664
|
+
mat-fab
|
|
1665
|
+
class="edit-fab"
|
|
1666
|
+
aria-label="Editar painéis"
|
|
1667
|
+
(click)="openEditor()"
|
|
1668
|
+
>
|
|
1669
|
+
<mat-icon fontIcon="edit"></mat-icon>
|
|
1670
|
+
</button>
|
|
1671
|
+
}
|
|
1672
|
+
`, 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
1673
|
}], propDecorators: { config: [{
|
|
895
1674
|
type: Input
|
|
896
1675
|
}], expansionId: [{
|
|
@@ -1097,9 +1876,9 @@ class PraxisExpansionConfigEditor {
|
|
|
1097
1876
|
const current = JSON.stringify(this.config || {});
|
|
1098
1877
|
this.isDirty$.next(current !== this.initialJson);
|
|
1099
1878
|
}
|
|
1100
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1101
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1102
|
-
<div class="pdx-expansion-editor">
|
|
1879
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansionConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
|
|
1880
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisExpansionConfigEditor, isStandalone: true, selector: "praxis-expansion-config-editor", inputs: { config: "config", expansionId: "expansionId" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1881
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1103
1882
|
<section class="sec">
|
|
1104
1883
|
<h4>Aparência</h4>
|
|
1105
1884
|
<div class="grid two">
|
|
@@ -1117,7 +1896,7 @@ class PraxisExpansionConfigEditor {
|
|
|
1117
1896
|
</select>
|
|
1118
1897
|
</mat-form-field>
|
|
1119
1898
|
</div>
|
|
1120
|
-
|
|
1899
|
+
|
|
1121
1900
|
<h5>Container</h5>
|
|
1122
1901
|
<div class="grid two">
|
|
1123
1902
|
<mat-form-field appearance="outline">
|
|
@@ -1159,7 +1938,7 @@ class PraxisExpansionConfigEditor {
|
|
|
1159
1938
|
<input matInput [value]="config?.appearance?.container?.panelGap || ''" (input)="setContainer('panelGap', $any($event.target).value)" placeholder="12px" />
|
|
1160
1939
|
</mat-form-field>
|
|
1161
1940
|
</div>
|
|
1162
|
-
|
|
1941
|
+
|
|
1163
1942
|
<h5>Cabeçalho</h5>
|
|
1164
1943
|
<div class="grid two">
|
|
1165
1944
|
<mat-form-field appearance="outline">
|
|
@@ -1219,7 +1998,7 @@ class PraxisExpansionConfigEditor {
|
|
|
1219
1998
|
<input matInput [value]="config?.appearance?.header?.toggleIconSize || ''" (input)="setHeader('toggleIconSize', $any($event.target).value)" placeholder="12px" />
|
|
1220
1999
|
</mat-form-field>
|
|
1221
2000
|
</div>
|
|
1222
|
-
|
|
2001
|
+
|
|
1223
2002
|
<h5>Estados</h5>
|
|
1224
2003
|
<div class="grid two">
|
|
1225
2004
|
<mat-form-field appearance="outline">
|
|
@@ -1265,13 +2044,15 @@ class PraxisExpansionConfigEditor {
|
|
|
1265
2044
|
type="button"
|
|
1266
2045
|
[matTooltip]="'Use tokens M3, por exemplo: var(--md-sys-color-primary)'"
|
|
1267
2046
|
matTooltipPosition="above"
|
|
1268
|
-
|
|
2047
|
+
>
|
|
1269
2048
|
<mat-icon [praxisIcon]="'help_outline'"></mat-icon>
|
|
1270
2049
|
</button>
|
|
1271
2050
|
</mat-form-field>
|
|
1272
|
-
|
|
2051
|
+
@if (tokensError) {
|
|
2052
|
+
<div class="error">{{ tokensError }}</div>
|
|
2053
|
+
}
|
|
1273
2054
|
</section>
|
|
1274
|
-
|
|
2055
|
+
|
|
1275
2056
|
<section class="sec">
|
|
1276
2057
|
<h4>Configuração do acordeão</h4>
|
|
1277
2058
|
<div class="grid two">
|
|
@@ -1302,42 +2083,47 @@ class PraxisExpansionConfigEditor {
|
|
|
1302
2083
|
<button mat-stroked-button color="primary" (click)="addPanel()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar painel</button>
|
|
1303
2084
|
</div>
|
|
1304
2085
|
</section>
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
<
|
|
1308
|
-
<
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
<
|
|
2086
|
+
|
|
2087
|
+
@for (p of (config?.panels || []); track p; let i = $index) {
|
|
2088
|
+
<section class="sec">
|
|
2089
|
+
<div class="panel-row">
|
|
2090
|
+
<strong>{{ p.title || ('Painel ' + (i + 1)) }}</strong>
|
|
2091
|
+
<span class="grow"></span>
|
|
2092
|
+
<button mat-button (click)="move(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon>Subir</button>
|
|
2093
|
+
<button mat-button (click)="move(i, 1)" [disabled]="i>=(config!.panels!.length-1)"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon>Descer</button>
|
|
2094
|
+
<button mat-button color="warn" (click)="removePanel(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon>Remover</button>
|
|
2095
|
+
</div>
|
|
2096
|
+
<div class="grid two">
|
|
2097
|
+
<mat-form-field appearance="outline"><mat-label>ID</mat-label>
|
|
1316
2098
|
<input matInput [value]="p.id || ''" (input)="setPanel(i, 'id', $any($event.target).value)" />
|
|
1317
2099
|
</mat-form-field>
|
|
1318
2100
|
<mat-form-field appearance="outline"><mat-label>Título</mat-label>
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
</
|
|
2101
|
+
<input matInput [value]="p.title || ''" (input)="setPanel(i, 'title', $any($event.target).value)" />
|
|
2102
|
+
</mat-form-field>
|
|
2103
|
+
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
2104
|
+
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
2105
|
+
</mat-form-field>
|
|
2106
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2107
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2108
|
+
</mat-form-field>
|
|
2109
|
+
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
2110
|
+
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
2111
|
+
</mat-form-field>
|
|
2112
|
+
<mat-form-field appearance="outline"><mat-label>Altura expandida</mat-label>
|
|
2113
|
+
<input matInput placeholder="64px" [value]="p.expandedHeight || ''" (input)="setPanel(i, 'expandedHeight', $any($event.target).value)" />
|
|
2114
|
+
</mat-form-field>
|
|
2115
|
+
<div class="row wrap">
|
|
2116
|
+
<mat-slide-toggle [checked]="p.disabled || false" (change)="setPanel(i, 'disabled', $event.checked)">Desativado</mat-slide-toggle>
|
|
2117
|
+
<mat-slide-toggle [checked]="p.expanded || false" (change)="setPanel(i, 'expanded', $event.checked)">Expandido</mat-slide-toggle>
|
|
2118
|
+
<mat-slide-toggle [checked]="p.hideToggle || false" (change)="setPanel(i, 'hideToggle', $event.checked)">Ocultar toggle</mat-slide-toggle>
|
|
2119
|
+
</div>
|
|
1337
2120
|
</div>
|
|
1338
|
-
|
|
2121
|
+
</section>
|
|
2122
|
+
}
|
|
2123
|
+
</div>
|
|
2124
|
+
`, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.pdx-expansion-editor{display:grid;gap:16px}.sec{border:1px solid var(--md-sys-color-outline-variant);padding:12px;border-radius:10px;background:var(--md-sys-color-surface-container-lowest);display:grid;gap:12px}.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.row.wrap{flex-wrap:wrap}.panel-row{display:flex;gap:8px;align-items:center;width:100%}.grow{flex:1 1 auto}.grid{display:grid;gap:10px;align-items:center}.grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.full{width:100%}.error{color:var(--md-sys-color-error);font-size:12px}.pdx-expansion-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1339
2125
|
}
|
|
1340
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansionConfigEditor, decorators: [{
|
|
1341
2127
|
type: Component,
|
|
1342
2128
|
args: [{ selector: 'praxis-expansion-config-editor', standalone: true, imports: [
|
|
1343
2129
|
CommonModule,
|
|
@@ -1349,7 +2135,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1349
2135
|
MatTooltipModule,
|
|
1350
2136
|
PraxisIconDirective,
|
|
1351
2137
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1352
|
-
<div class="pdx-expansion-editor">
|
|
2138
|
+
<div class="pdx-expansion-editor" data-testid="expansion-config-editor">
|
|
1353
2139
|
<section class="sec">
|
|
1354
2140
|
<h4>Aparência</h4>
|
|
1355
2141
|
<div class="grid two">
|
|
@@ -1367,7 +2153,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1367
2153
|
</select>
|
|
1368
2154
|
</mat-form-field>
|
|
1369
2155
|
</div>
|
|
1370
|
-
|
|
2156
|
+
|
|
1371
2157
|
<h5>Container</h5>
|
|
1372
2158
|
<div class="grid two">
|
|
1373
2159
|
<mat-form-field appearance="outline">
|
|
@@ -1409,7 +2195,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1409
2195
|
<input matInput [value]="config?.appearance?.container?.panelGap || ''" (input)="setContainer('panelGap', $any($event.target).value)" placeholder="12px" />
|
|
1410
2196
|
</mat-form-field>
|
|
1411
2197
|
</div>
|
|
1412
|
-
|
|
2198
|
+
|
|
1413
2199
|
<h5>Cabeçalho</h5>
|
|
1414
2200
|
<div class="grid two">
|
|
1415
2201
|
<mat-form-field appearance="outline">
|
|
@@ -1469,7 +2255,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1469
2255
|
<input matInput [value]="config?.appearance?.header?.toggleIconSize || ''" (input)="setHeader('toggleIconSize', $any($event.target).value)" placeholder="12px" />
|
|
1470
2256
|
</mat-form-field>
|
|
1471
2257
|
</div>
|
|
1472
|
-
|
|
2258
|
+
|
|
1473
2259
|
<h5>Estados</h5>
|
|
1474
2260
|
<div class="grid two">
|
|
1475
2261
|
<mat-form-field appearance="outline">
|
|
@@ -1515,13 +2301,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1515
2301
|
type="button"
|
|
1516
2302
|
[matTooltip]="'Use tokens M3, por exemplo: var(--md-sys-color-primary)'"
|
|
1517
2303
|
matTooltipPosition="above"
|
|
1518
|
-
|
|
2304
|
+
>
|
|
1519
2305
|
<mat-icon [praxisIcon]="'help_outline'"></mat-icon>
|
|
1520
2306
|
</button>
|
|
1521
2307
|
</mat-form-field>
|
|
1522
|
-
|
|
2308
|
+
@if (tokensError) {
|
|
2309
|
+
<div class="error">{{ tokensError }}</div>
|
|
2310
|
+
}
|
|
1523
2311
|
</section>
|
|
1524
|
-
|
|
2312
|
+
|
|
1525
2313
|
<section class="sec">
|
|
1526
2314
|
<h4>Configuração do acordeão</h4>
|
|
1527
2315
|
<div class="grid two">
|
|
@@ -1552,40 +2340,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1552
2340
|
<button mat-stroked-button color="primary" (click)="addPanel()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar painel</button>
|
|
1553
2341
|
</div>
|
|
1554
2342
|
</section>
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
<
|
|
1558
|
-
<
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
<
|
|
2343
|
+
|
|
2344
|
+
@for (p of (config?.panels || []); track p; let i = $index) {
|
|
2345
|
+
<section class="sec">
|
|
2346
|
+
<div class="panel-row">
|
|
2347
|
+
<strong>{{ p.title || ('Painel ' + (i + 1)) }}</strong>
|
|
2348
|
+
<span class="grow"></span>
|
|
2349
|
+
<button mat-button (click)="move(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon>Subir</button>
|
|
2350
|
+
<button mat-button (click)="move(i, 1)" [disabled]="i>=(config!.panels!.length-1)"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon>Descer</button>
|
|
2351
|
+
<button mat-button color="warn" (click)="removePanel(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon>Remover</button>
|
|
2352
|
+
</div>
|
|
2353
|
+
<div class="grid two">
|
|
2354
|
+
<mat-form-field appearance="outline"><mat-label>ID</mat-label>
|
|
1566
2355
|
<input matInput [value]="p.id || ''" (input)="setPanel(i, 'id', $any($event.target).value)" />
|
|
1567
2356
|
</mat-form-field>
|
|
1568
2357
|
<mat-form-field appearance="outline"><mat-label>Título</mat-label>
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
</
|
|
2358
|
+
<input matInput [value]="p.title || ''" (input)="setPanel(i, 'title', $any($event.target).value)" />
|
|
2359
|
+
</mat-form-field>
|
|
2360
|
+
<mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
|
|
2361
|
+
<input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
|
|
2362
|
+
</mat-form-field>
|
|
2363
|
+
<mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
|
|
2364
|
+
<input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
|
|
2365
|
+
</mat-form-field>
|
|
2366
|
+
<mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
|
|
2367
|
+
<input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
|
|
2368
|
+
</mat-form-field>
|
|
2369
|
+
<mat-form-field appearance="outline"><mat-label>Altura expandida</mat-label>
|
|
2370
|
+
<input matInput placeholder="64px" [value]="p.expandedHeight || ''" (input)="setPanel(i, 'expandedHeight', $any($event.target).value)" />
|
|
2371
|
+
</mat-form-field>
|
|
2372
|
+
<div class="row wrap">
|
|
2373
|
+
<mat-slide-toggle [checked]="p.disabled || false" (change)="setPanel(i, 'disabled', $event.checked)">Desativado</mat-slide-toggle>
|
|
2374
|
+
<mat-slide-toggle [checked]="p.expanded || false" (change)="setPanel(i, 'expanded', $event.checked)">Expandido</mat-slide-toggle>
|
|
2375
|
+
<mat-slide-toggle [checked]="p.hideToggle || false" (change)="setPanel(i, 'hideToggle', $event.checked)">Ocultar toggle</mat-slide-toggle>
|
|
2376
|
+
</div>
|
|
2377
|
+
</div>
|
|
2378
|
+
</section>
|
|
2379
|
+
}
|
|
1587
2380
|
</div>
|
|
1588
|
-
|
|
2381
|
+
`, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.pdx-expansion-editor{display:grid;gap:16px}.sec{border:1px solid var(--md-sys-color-outline-variant);padding:12px;border-radius:10px;background:var(--md-sys-color-surface-container-lowest);display:grid;gap:12px}.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.row.wrap{flex-wrap:wrap}.panel-row{display:flex;gap:8px;align-items:center;width:100%}.grow{flex:1 1 auto}.grid{display:grid;gap:10px;align-items:center}.grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.full{width:100%}.error{color:var(--md-sys-color-error);font-size:12px}.pdx-expansion-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"] }]
|
|
1589
2382
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1590
2383
|
type: Inject,
|
|
1591
2384
|
args: [SETTINGS_PANEL_DATA]
|
|
@@ -1595,6 +2388,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1595
2388
|
type: Input
|
|
1596
2389
|
}] } });
|
|
1597
2390
|
|
|
2391
|
+
class PraxisExpansionWidgetConfigEditor {
|
|
2392
|
+
inputs = null;
|
|
2393
|
+
widgetKey;
|
|
2394
|
+
expansionEditor;
|
|
2395
|
+
isDirty$ = new BehaviorSubject(false);
|
|
2396
|
+
isValid$ = new BehaviorSubject(true);
|
|
2397
|
+
isBusy$ = new BehaviorSubject(false);
|
|
2398
|
+
subscription = new Subscription();
|
|
2399
|
+
emptyConfig = {};
|
|
2400
|
+
get config() {
|
|
2401
|
+
return this.inputs?.config ?? this.emptyConfig;
|
|
2402
|
+
}
|
|
2403
|
+
get expansionId() {
|
|
2404
|
+
return this.inputs?.expansionId ?? this.widgetKey;
|
|
2405
|
+
}
|
|
2406
|
+
ngAfterViewInit() {
|
|
2407
|
+
if (!this.expansionEditor) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
this.subscription.add(this.expansionEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
|
|
2411
|
+
this.subscription.add(this.expansionEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
|
|
2412
|
+
this.subscription.add(this.expansionEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
|
|
2413
|
+
}
|
|
2414
|
+
ngOnDestroy() {
|
|
2415
|
+
this.subscription.unsubscribe();
|
|
2416
|
+
}
|
|
2417
|
+
getSettingsValue() {
|
|
2418
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2419
|
+
}
|
|
2420
|
+
onSave() {
|
|
2421
|
+
return this.buildValue(this.expansionEditor?.getSettingsValue());
|
|
2422
|
+
}
|
|
2423
|
+
reset() {
|
|
2424
|
+
this.expansionEditor?.reset();
|
|
2425
|
+
}
|
|
2426
|
+
buildValue(config) {
|
|
2427
|
+
return {
|
|
2428
|
+
inputs: {
|
|
2429
|
+
...(this.inputs ?? {}),
|
|
2430
|
+
config: config ?? this.config,
|
|
2431
|
+
...(this.expansionId ? { expansionId: this.expansionId } : {}),
|
|
2432
|
+
},
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2436
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", 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: `
|
|
2437
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2438
|
+
<praxis-expansion-config-editor
|
|
2439
|
+
#expansionEditor
|
|
2440
|
+
[config]="config"
|
|
2441
|
+
[expansionId]="expansionId"
|
|
2442
|
+
/>
|
|
2443
|
+
</section>
|
|
2444
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PraxisExpansionConfigEditor, selector: "praxis-expansion-config-editor", inputs: ["config", "expansionId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2445
|
+
}
|
|
2446
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, decorators: [{
|
|
2447
|
+
type: Component,
|
|
2448
|
+
args: [{
|
|
2449
|
+
selector: 'praxis-expansion-widget-config-editor',
|
|
2450
|
+
standalone: true,
|
|
2451
|
+
imports: [PraxisExpansionConfigEditor],
|
|
2452
|
+
template: `
|
|
2453
|
+
<section data-testid="expansion-widget-config-editor">
|
|
2454
|
+
<praxis-expansion-config-editor
|
|
2455
|
+
#expansionEditor
|
|
2456
|
+
[config]="config"
|
|
2457
|
+
[expansionId]="expansionId"
|
|
2458
|
+
/>
|
|
2459
|
+
</section>
|
|
2460
|
+
`,
|
|
2461
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2462
|
+
}]
|
|
2463
|
+
}], propDecorators: { inputs: [{
|
|
2464
|
+
type: Input
|
|
2465
|
+
}], widgetKey: [{
|
|
2466
|
+
type: Input
|
|
2467
|
+
}], expansionEditor: [{
|
|
2468
|
+
type: ViewChild,
|
|
2469
|
+
args: ['expansionEditor']
|
|
2470
|
+
}] } });
|
|
2471
|
+
|
|
2472
|
+
const PRAXIS_EXPANSION_PORTS = [
|
|
2473
|
+
{
|
|
2474
|
+
id: 'config',
|
|
2475
|
+
label: 'Configuracao',
|
|
2476
|
+
direction: 'input',
|
|
2477
|
+
semanticKind: 'config-fragment',
|
|
2478
|
+
schema: {
|
|
2479
|
+
id: 'ExpansionMetadata',
|
|
2480
|
+
kind: 'ts-type',
|
|
2481
|
+
ref: 'ExpansionMetadata',
|
|
2482
|
+
},
|
|
2483
|
+
description: 'Fragmento canonico de configuracao dos paineis e dos widgets internos.',
|
|
2484
|
+
exposure: { public: true, group: 'config' },
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
id: 'widgetEvent',
|
|
2488
|
+
label: 'Evento interno de widget',
|
|
2489
|
+
direction: 'output',
|
|
2490
|
+
semanticKind: 'event',
|
|
2491
|
+
schema: {
|
|
2492
|
+
id: 'WidgetEventEnvelope',
|
|
2493
|
+
kind: 'ts-type',
|
|
2494
|
+
ref: 'WidgetEventEnvelope',
|
|
2495
|
+
},
|
|
2496
|
+
cardinality: 'stream',
|
|
2497
|
+
description: 'Bridge composta para transporte de eventos internos. As portas canonicas dos filhos sao resolvidas por component-port + nestedPath.',
|
|
2498
|
+
exposure: { public: true, advanced: true, group: 'composite' },
|
|
2499
|
+
},
|
|
2500
|
+
];
|
|
1598
2501
|
const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
1599
2502
|
id: 'praxis-expansion',
|
|
1600
2503
|
selector: 'praxis-expansion',
|
|
@@ -1602,6 +2505,14 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1602
2505
|
friendlyName: 'Praxis Expansion Panel',
|
|
1603
2506
|
description: 'Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.',
|
|
1604
2507
|
icon: 'unfold_more',
|
|
2508
|
+
authoringManifestRef: {
|
|
2509
|
+
componentId: 'praxis-expansion',
|
|
2510
|
+
source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
|
|
2511
|
+
},
|
|
2512
|
+
configEditor: {
|
|
2513
|
+
component: PraxisExpansionWidgetConfigEditor,
|
|
2514
|
+
title: 'Configure expansion',
|
|
2515
|
+
},
|
|
1605
2516
|
inputs: [
|
|
1606
2517
|
{ name: 'config', type: 'ExpansionMetadata', label: 'Configuração', description: 'Configuração JSON (acordeão, aparência e painéis)' },
|
|
1607
2518
|
{ name: 'expansionId', type: 'string', label: 'ID', description: 'Identificador para persistência (obrigatório)' },
|
|
@@ -1676,6 +2587,7 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
|
|
|
1676
2587
|
],
|
|
1677
2588
|
tags: ['widget', 'expansion', 'accordion', 'configurable'],
|
|
1678
2589
|
lib: '@praxisui/expansion',
|
|
2590
|
+
ports: PRAXIS_EXPANSION_PORTS,
|
|
1679
2591
|
};
|
|
1680
2592
|
function providePraxisExpansionMetadata() {
|
|
1681
2593
|
return {
|
|
@@ -1691,6 +2603,315 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1691
2603
|
return { provide: MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, useValue: opts };
|
|
1692
2604
|
}
|
|
1693
2605
|
|
|
2606
|
+
const panelItemSchema = {
|
|
2607
|
+
type: 'object',
|
|
2608
|
+
required: ['id', 'title'],
|
|
2609
|
+
properties: {
|
|
2610
|
+
id: { type: 'string' },
|
|
2611
|
+
title: { type: 'string' },
|
|
2612
|
+
description: { type: 'string' },
|
|
2613
|
+
icon: { type: 'string' },
|
|
2614
|
+
disabled: { type: 'boolean' },
|
|
2615
|
+
expanded: { type: 'boolean' },
|
|
2616
|
+
hideToggle: { type: 'boolean' },
|
|
2617
|
+
collapsedHeight: { type: 'string' },
|
|
2618
|
+
expandedHeight: { type: 'string' },
|
|
2619
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2620
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2621
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2622
|
+
},
|
|
2623
|
+
};
|
|
2624
|
+
const panelPatchSchema = {
|
|
2625
|
+
type: 'object',
|
|
2626
|
+
minProperties: 1,
|
|
2627
|
+
properties: {
|
|
2628
|
+
id: { type: 'string' },
|
|
2629
|
+
title: { type: 'string' },
|
|
2630
|
+
description: { type: 'string' },
|
|
2631
|
+
icon: { type: 'string' },
|
|
2632
|
+
disabled: { type: 'boolean' },
|
|
2633
|
+
expanded: { type: 'boolean' },
|
|
2634
|
+
hideToggle: { type: 'boolean' },
|
|
2635
|
+
collapsedHeight: { type: 'string' },
|
|
2636
|
+
expandedHeight: { type: 'string' },
|
|
2637
|
+
content: { type: 'array', items: { type: 'object' } },
|
|
2638
|
+
widgets: { type: 'array', items: { type: 'object' } },
|
|
2639
|
+
actionButtons: { type: 'array', items: { type: 'object' } },
|
|
2640
|
+
},
|
|
2641
|
+
};
|
|
2642
|
+
const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
|
|
2643
|
+
schemaVersion: '1.0.0',
|
|
2644
|
+
componentId: 'praxis-expansion',
|
|
2645
|
+
ownerPackage: '@praxisui/expansion',
|
|
2646
|
+
configSchemaId: 'ExpansionMetadata',
|
|
2647
|
+
manifestVersion: '1.0.0',
|
|
2648
|
+
runtimeInputs: [
|
|
2649
|
+
{ name: 'config', type: 'ExpansionMetadata', description: 'Canonical accordion and panel configuration.' },
|
|
2650
|
+
{ name: 'expansionId', type: 'string', description: 'Stable id used to derive expansion config persistence scope.' },
|
|
2651
|
+
{ name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
|
|
2652
|
+
{ name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
|
|
2653
|
+
{ name: 'strictValidation', type: 'boolean', description: 'Controls nested widget validation strictness.' },
|
|
2654
|
+
{ name: 'defaultOptions', type: 'MatExpansionPanelDefaultOptions', description: 'Instance-level Material expansion defaults.' },
|
|
2655
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
|
|
2656
|
+
],
|
|
2657
|
+
editableTargets: [
|
|
2658
|
+
{ kind: 'panel', resolver: 'panel-by-id-or-title', description: 'A panel in config.panels[].' },
|
|
2659
|
+
{ kind: 'panelHeader', resolver: 'panel-by-id-or-title', description: 'Header title, description, icon and heights for a panel.' },
|
|
2660
|
+
{ kind: 'panelContent', resolver: 'panel-content-by-id', description: 'Lazy panel content hosted through fields, widgets or action buttons.' },
|
|
2661
|
+
{ kind: 'expandedState', resolver: 'panel-by-id-or-title', description: 'Panel expanded state and default expanded selection.' },
|
|
2662
|
+
{ kind: 'disabledState', resolver: 'panel-by-id-or-title', description: 'Panel disabled state.' },
|
|
2663
|
+
{ kind: 'layout', resolver: 'expansion-layout-config', description: 'Accordion display mode, toggle position, density and visual layout.' },
|
|
2664
|
+
{ kind: 'behavior', resolver: 'expansion-behavior-config', description: 'Accordion multi-expand and hide-toggle behavior.' },
|
|
2665
|
+
],
|
|
2666
|
+
operations: [
|
|
2667
|
+
{
|
|
2668
|
+
operationId: 'panel.add',
|
|
2669
|
+
title: 'Add panel',
|
|
2670
|
+
scope: 'global',
|
|
2671
|
+
targetKind: 'panel',
|
|
2672
|
+
target: { kind: 'panel', resolver: 'panels-array', ambiguityPolicy: 'fail', required: false },
|
|
2673
|
+
inputSchema: panelItemSchema,
|
|
2674
|
+
effects: [{ kind: 'append-unique', path: 'panels[]', key: 'id' }],
|
|
2675
|
+
destructive: false,
|
|
2676
|
+
requiresConfirmation: false,
|
|
2677
|
+
validators: ['panel-id-unique', 'panel-order-deterministic', 'panel-content-valid'],
|
|
2678
|
+
affectedPaths: ['panels[]'],
|
|
2679
|
+
submissionImpact: 'config-only',
|
|
2680
|
+
preconditions: ['config-initialized'],
|
|
2681
|
+
},
|
|
2682
|
+
{
|
|
2683
|
+
operationId: 'panel.remove',
|
|
2684
|
+
title: 'Remove panel',
|
|
2685
|
+
scope: 'layout',
|
|
2686
|
+
targetKind: 'panel',
|
|
2687
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2688
|
+
inputSchema: {
|
|
2689
|
+
type: 'object',
|
|
2690
|
+
properties: {
|
|
2691
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2692
|
+
},
|
|
2693
|
+
},
|
|
2694
|
+
effects: [{
|
|
2695
|
+
kind: 'compile-domain-patch',
|
|
2696
|
+
handler: 'expansion-panel-remove',
|
|
2697
|
+
handlerContract: {
|
|
2698
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2699
|
+
writes: ['panels[]', 'panels[].expanded'],
|
|
2700
|
+
identityKeys: ['panels[].id'],
|
|
2701
|
+
inputSchema: {
|
|
2702
|
+
type: 'object',
|
|
2703
|
+
properties: {
|
|
2704
|
+
replacementExpandedPanelId: { type: 'string' },
|
|
2705
|
+
},
|
|
2706
|
+
},
|
|
2707
|
+
failureModes: ['panel-not-found', 'replacement-panel-not-found', 'content-removal-not-confirmed', 'single-expand-conflict'],
|
|
2708
|
+
description: 'Removes the target panel by stable id and applies replacement expanded state when the removed panel was expanded.',
|
|
2709
|
+
},
|
|
2710
|
+
}],
|
|
2711
|
+
destructive: true,
|
|
2712
|
+
requiresConfirmation: true,
|
|
2713
|
+
validators: ['panel-exists', 'default-expanded-removal-safe', 'panel-content-removal-confirmed'],
|
|
2714
|
+
affectedPaths: ['panels[]', 'panels[].expanded'],
|
|
2715
|
+
submissionImpact: 'config-only',
|
|
2716
|
+
preconditions: ['config-initialized', 'target-panel-exists', 'confirmation-collected'],
|
|
2717
|
+
},
|
|
2718
|
+
{
|
|
2719
|
+
operationId: 'panel.title.set',
|
|
2720
|
+
title: 'Set panel title',
|
|
2721
|
+
scope: 'layout',
|
|
2722
|
+
targetKind: 'panelHeader',
|
|
2723
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2724
|
+
inputSchema: { type: 'object', required: ['title'], properties: { title: { type: 'string' } } },
|
|
2725
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2726
|
+
destructive: false,
|
|
2727
|
+
requiresConfirmation: false,
|
|
2728
|
+
validators: ['panel-exists', 'panel-title-valid'],
|
|
2729
|
+
affectedPaths: ['panels[].title'],
|
|
2730
|
+
submissionImpact: 'config-only',
|
|
2731
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
operationId: 'panel.description.set',
|
|
2735
|
+
title: 'Set panel description',
|
|
2736
|
+
scope: 'layout',
|
|
2737
|
+
targetKind: 'panelHeader',
|
|
2738
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2739
|
+
inputSchema: { type: 'object', required: ['description'], properties: { description: { type: 'string' } } },
|
|
2740
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2741
|
+
destructive: false,
|
|
2742
|
+
requiresConfirmation: false,
|
|
2743
|
+
validators: ['panel-exists', 'panel-description-valid'],
|
|
2744
|
+
affectedPaths: ['panels[].description'],
|
|
2745
|
+
submissionImpact: 'config-only',
|
|
2746
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2747
|
+
},
|
|
2748
|
+
{
|
|
2749
|
+
operationId: 'panel.icon.set',
|
|
2750
|
+
title: 'Set panel icon',
|
|
2751
|
+
scope: 'layout',
|
|
2752
|
+
targetKind: 'panelHeader',
|
|
2753
|
+
target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2754
|
+
inputSchema: { type: 'object', required: ['icon'], properties: { icon: { type: 'string' } } },
|
|
2755
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2756
|
+
destructive: false,
|
|
2757
|
+
requiresConfirmation: false,
|
|
2758
|
+
validators: ['panel-exists', 'panel-icon-valid', 'editor-runtime-round-trip'],
|
|
2759
|
+
affectedPaths: ['panels[].icon'],
|
|
2760
|
+
submissionImpact: 'config-only',
|
|
2761
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2762
|
+
},
|
|
2763
|
+
{
|
|
2764
|
+
operationId: 'panel.order.set',
|
|
2765
|
+
title: 'Reorder panels',
|
|
2766
|
+
scope: 'layout',
|
|
2767
|
+
targetKind: 'panel',
|
|
2768
|
+
target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2769
|
+
inputSchema: { type: 'object', required: ['beforePanelId'], properties: { beforePanelId: { type: 'string' } } },
|
|
2770
|
+
effects: [{ kind: 'reorder-by-key', path: 'panels[]', key: 'id' }],
|
|
2771
|
+
destructive: false,
|
|
2772
|
+
requiresConfirmation: false,
|
|
2773
|
+
validators: ['panel-exists', 'panel-order-deterministic'],
|
|
2774
|
+
affectedPaths: ['panels[]'],
|
|
2775
|
+
submissionImpact: 'config-only',
|
|
2776
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
operationId: 'panel.disabled.set',
|
|
2780
|
+
title: 'Set disabled state',
|
|
2781
|
+
scope: 'interaction',
|
|
2782
|
+
targetKind: 'disabledState',
|
|
2783
|
+
target: { kind: 'disabledState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2784
|
+
inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
|
|
2785
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2786
|
+
destructive: false,
|
|
2787
|
+
requiresConfirmation: false,
|
|
2788
|
+
validators: ['panel-exists', 'disabled-expanded-compatible'],
|
|
2789
|
+
affectedPaths: ['panels[].disabled', 'panels[].expanded'],
|
|
2790
|
+
submissionImpact: 'config-only',
|
|
2791
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
operationId: 'behavior.multiExpand.set',
|
|
2795
|
+
title: 'Set multi-expand behavior',
|
|
2796
|
+
scope: 'interaction',
|
|
2797
|
+
targetKind: 'behavior',
|
|
2798
|
+
target: { kind: 'behavior', resolver: 'expansion-behavior-config', ambiguityPolicy: 'fail', required: true },
|
|
2799
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2800
|
+
effects: [{
|
|
2801
|
+
kind: 'compile-domain-patch',
|
|
2802
|
+
handler: 'expansion-multi-expand-set',
|
|
2803
|
+
handlerContract: {
|
|
2804
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
|
|
2805
|
+
writes: ['accordion.multi', 'panels[].expanded'],
|
|
2806
|
+
identityKeys: ['panels[].id'],
|
|
2807
|
+
inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
|
|
2808
|
+
failureModes: ['multiple-expanded-panels-conflict', 'panel-id-missing'],
|
|
2809
|
+
description: 'Sets accordion.multi and collapses competing expanded panels when switching to single-expand behavior.',
|
|
2810
|
+
},
|
|
2811
|
+
}],
|
|
2812
|
+
destructive: false,
|
|
2813
|
+
requiresConfirmation: false,
|
|
2814
|
+
validators: ['multi-expand-default-compatible', 'accordion-values-valid', 'editor-runtime-round-trip'],
|
|
2815
|
+
affectedPaths: ['accordion.multi', 'panels[].expanded'],
|
|
2816
|
+
submissionImpact: 'config-only',
|
|
2817
|
+
preconditions: ['config-initialized'],
|
|
2818
|
+
},
|
|
2819
|
+
{
|
|
2820
|
+
operationId: 'behavior.defaultExpanded.set',
|
|
2821
|
+
title: 'Set default expanded panel',
|
|
2822
|
+
scope: 'interaction',
|
|
2823
|
+
targetKind: 'expandedState',
|
|
2824
|
+
target: { kind: 'expandedState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
2825
|
+
inputSchema: {
|
|
2826
|
+
type: 'object',
|
|
2827
|
+
required: ['expanded'],
|
|
2828
|
+
properties: {
|
|
2829
|
+
expanded: { type: 'boolean' },
|
|
2830
|
+
collapseOthers: { type: 'boolean' },
|
|
2831
|
+
},
|
|
2832
|
+
},
|
|
2833
|
+
effects: [{
|
|
2834
|
+
kind: 'compile-domain-patch',
|
|
2835
|
+
handler: 'expansion-default-expanded-upsert',
|
|
2836
|
+
handlerContract: {
|
|
2837
|
+
reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded', 'panels[].disabled'],
|
|
2838
|
+
writes: ['panels[].expanded'],
|
|
2839
|
+
identityKeys: ['panels[].id'],
|
|
2840
|
+
inputSchema: {
|
|
2841
|
+
type: 'object',
|
|
2842
|
+
required: ['panelId', 'expanded'],
|
|
2843
|
+
properties: {
|
|
2844
|
+
panelId: { type: 'string' },
|
|
2845
|
+
expanded: { type: 'boolean' },
|
|
2846
|
+
collapseOthers: { type: 'boolean' },
|
|
2847
|
+
},
|
|
2848
|
+
},
|
|
2849
|
+
failureModes: ['panel-not-found', 'panel-disabled', 'single-expand-conflict'],
|
|
2850
|
+
description: 'Sets a panel expanded state and collapses competing panels when accordion.multi is false.',
|
|
2851
|
+
},
|
|
2852
|
+
}],
|
|
2853
|
+
validators: ['panel-exists', 'default-expanded-panel-exists', 'multi-expand-default-compatible', 'disabled-expanded-compatible'],
|
|
2854
|
+
destructive: false,
|
|
2855
|
+
requiresConfirmation: false,
|
|
2856
|
+
affectedPaths: ['panels[].expanded', 'accordion.multi'],
|
|
2857
|
+
submissionImpact: 'config-only',
|
|
2858
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2859
|
+
},
|
|
2860
|
+
{
|
|
2861
|
+
operationId: 'panel.content.set',
|
|
2862
|
+
title: 'Set panel content',
|
|
2863
|
+
scope: 'layout',
|
|
2864
|
+
targetKind: 'panelContent',
|
|
2865
|
+
target: { kind: 'panelContent', resolver: 'panel-content-by-id', ambiguityPolicy: 'fail', required: true },
|
|
2866
|
+
inputSchema: panelPatchSchema,
|
|
2867
|
+
effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
|
|
2868
|
+
destructive: false,
|
|
2869
|
+
requiresConfirmation: false,
|
|
2870
|
+
validators: ['panel-exists', 'panel-content-valid', 'lazy-content-compatible', 'nested-widget-contract-delegated'],
|
|
2871
|
+
affectedPaths: ['panels[].content', 'panels[].widgets', 'panels[].actionButtons'],
|
|
2872
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
2873
|
+
preconditions: ['config-initialized', 'target-panel-exists'],
|
|
2874
|
+
},
|
|
2875
|
+
],
|
|
2876
|
+
validators: [
|
|
2877
|
+
{ validatorId: 'panel-id-unique', level: 'error', code: 'PEXP001', description: 'Panel ids must be unique and stable within config.panels[].' },
|
|
2878
|
+
{ validatorId: 'panel-exists', level: 'error', code: 'PEXP002', description: 'Target panel must exist before applying the operation.' },
|
|
2879
|
+
{ validatorId: 'panel-order-deterministic', level: 'error', code: 'PEXP003', description: 'Panel ordering must use stable ids, not transient array index as identity.' },
|
|
2880
|
+
{ validatorId: 'panel-title-valid', level: 'error', code: 'PEXP004', description: 'Panel title must be a non-empty text value after localization/domain projection.' },
|
|
2881
|
+
{ validatorId: 'panel-description-valid', level: 'warning', code: 'PEXP005', description: 'Panel description should remain plain header-support text and not replace panel content.' },
|
|
2882
|
+
{ validatorId: 'panel-icon-valid', level: 'warning', code: 'PEXP006', description: 'Panel icon metadata must remain compatible with PraxisIconDirective and editor round-trip.' },
|
|
2883
|
+
{ validatorId: 'panel-content-valid', level: 'error', code: 'PEXP007', description: 'Panel content must remain valid FieldMetadata[], WidgetDefinition[] or action button metadata.' },
|
|
2884
|
+
{ validatorId: 'panel-content-removal-confirmed', level: 'error', code: 'PEXP008', description: 'Removing a panel with fields, widgets or action buttons is destructive and requires confirmation.' },
|
|
2885
|
+
{ validatorId: 'default-expanded-panel-exists', level: 'error', code: 'PEXP009', description: 'Default expanded state must reference an existing panel id.' },
|
|
2886
|
+
{ validatorId: 'default-expanded-removal-safe', level: 'error', code: 'PEXP010', description: 'Removing an expanded panel requires deterministic replacement state or explicit confirmation.' },
|
|
2887
|
+
{ 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.' },
|
|
2888
|
+
{ validatorId: 'disabled-expanded-compatible', level: 'warning', code: 'PEXP012', description: 'A disabled panel should not be the only expanded/default focus target without explicit intent.' },
|
|
2889
|
+
{ validatorId: 'accordion-values-valid', level: 'error', code: 'PEXP013', description: 'Accordion behavior values must match ExpansionMetadata and Angular Material expansion bindings.' },
|
|
2890
|
+
{ validatorId: 'lazy-content-compatible', level: 'info', code: 'PEXP015', description: 'Panel content remains lazy through matExpansionPanelContent and should not require eager child runtime state.' },
|
|
2891
|
+
{ validatorId: 'nested-widget-contract-delegated', level: 'info', code: 'PEXP016', description: 'Nested widget content remains governed by child component contracts and component-port nestedPath semantics.' },
|
|
2892
|
+
{ 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.' },
|
|
2893
|
+
],
|
|
2894
|
+
roundTripRequirements: [
|
|
2895
|
+
'Operations must preserve stable panel ids; array index may be used only as a resolver fallback, never as canonical identity.',
|
|
2896
|
+
'Settings Panel editor, runtime persistence and registry projection must round-trip ExpansionMetadata without losing panel ids, order, icons or expanded state.',
|
|
2897
|
+
'When accordion.multi is false, authoring must collapse competing panels or fail validation instead of producing multiple default-expanded panels.',
|
|
2898
|
+
'Lazy panel content remains represented by panels[].content, panels[].widgets and panels[].actionButtons; authoring cannot require eager child component instances.',
|
|
2899
|
+
'Nested widgets remain delegated through WidgetDefinition and component-port nestedPath semantics instead of being redefined by the expansion contract.',
|
|
2900
|
+
],
|
|
2901
|
+
examples: [
|
|
2902
|
+
{ id: 'add-summary-panel', request: 'Add a summary panel before the audit panel.', operationId: 'panel.add', params: { id: 'summary', title: 'Summary' }, isPositive: true },
|
|
2903
|
+
{ id: 'rename-details-panel', request: 'Rename details to Account details.', operationId: 'panel.title.set', target: 'details', params: { title: 'Account details' }, isPositive: true },
|
|
2904
|
+
{ id: 'describe-audit-panel', request: 'Set audit panel description to Recent changes.', operationId: 'panel.description.set', target: 'audit', params: { description: 'Recent changes' }, isPositive: true },
|
|
2905
|
+
{ id: 'set-panel-icon', request: 'Use the info icon on the summary panel.', operationId: 'panel.icon.set', target: 'summary', params: { icon: 'info' }, isPositive: true },
|
|
2906
|
+
{ 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 },
|
|
2907
|
+
{ id: 'enable-multi-expand', request: 'Allow multiple panels to stay open.', operationId: 'behavior.multiExpand.set', params: { multi: true }, isPositive: true },
|
|
2908
|
+
{ id: 'disable-archive-panel', request: 'Disable the archive panel.', operationId: 'panel.disabled.set', target: 'archive', params: { disabled: true }, isPositive: true },
|
|
2909
|
+
{ id: 'reject-missing-default-expanded', request: 'Open the missing panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'missing', params: { expanded: true }, isPositive: false },
|
|
2910
|
+
{ id: 'reject-duplicate-panel-id', request: 'Add another panel with id summary.', operationId: 'panel.add', params: { id: 'summary', title: 'Duplicate summary' }, isPositive: false },
|
|
2911
|
+
{ id: 'confirm-remove-content-panel', request: 'Remove the details panel that contains widgets.', operationId: 'panel.remove', target: 'details', params: { replacementExpandedPanelId: 'summary' }, isPositive: true },
|
|
2912
|
+
],
|
|
2913
|
+
};
|
|
2914
|
+
|
|
1694
2915
|
/*
|
|
1695
2916
|
* Public API Surface of praxis-expansion
|
|
1696
2917
|
*/
|
|
@@ -1699,4 +2920,4 @@ function providePraxisExpansionDefaults(opts) {
|
|
|
1699
2920
|
* Generated bundle index. Do not edit.
|
|
1700
2921
|
*/
|
|
1701
2922
|
|
|
1702
|
-
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|
|
2923
|
+
export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, PraxisExpansionWidgetConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
|