@praxisui/charts 1.0.0-beta.68

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.
@@ -0,0 +1,2662 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { Injectable, Inject, InjectionToken, input, output, viewChild, inject, DestroyRef, signal, computed, effect, ChangeDetectionStrategy, Component, ENVIRONMENT_INITIALIZER } from '@angular/core';
4
+ import * as i1$1 from '@praxisui/core';
5
+ import { buildApiUrl, API_URL, PraxisI18nService, ComponentMetadataRegistry, createDefaultTableConfig, DynamicWidgetPageComponent, DynamicGridPageComponent } from '@praxisui/core';
6
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
+ import { use, init } from 'echarts/core';
8
+ import { BarChart, LineChart, PieChart } from 'echarts/charts';
9
+ import { AriaComponent, DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent, TransformComponent } from 'echarts/components';
10
+ import { CanvasRenderer } from 'echarts/renderers';
11
+ import * as i1 from '@angular/common/http';
12
+ import { HttpErrorResponse } from '@angular/common/http';
13
+ import { throwError, map } from 'rxjs';
14
+ import { catchError } from 'rxjs/operators';
15
+ import { PraxisTable } from '@praxisui/table';
16
+
17
+ class PraxisChartDataTransformerService {
18
+ transform(config, rows) {
19
+ if (config.type === 'pie' || config.type === 'donut') {
20
+ return this.transformPie(config, rows);
21
+ }
22
+ return this.transformCartesian(config, rows);
23
+ }
24
+ transformCartesian(config, rows) {
25
+ const categoryField = config.axes?.x?.field;
26
+ if (!categoryField || !rows.length || !config.series.length) {
27
+ return {
28
+ mode: 'cartesian',
29
+ categories: [],
30
+ series: [],
31
+ slices: [],
32
+ hasData: false,
33
+ };
34
+ }
35
+ const categories = Array.from(new Set(rows.map((row) => this.normalizeCategory(row[categoryField]))));
36
+ const series = config.series.map((seriesConfig) => this.buildCartesianSeries(config, seriesConfig, rows, categories));
37
+ return {
38
+ mode: 'cartesian',
39
+ categories,
40
+ series,
41
+ slices: [],
42
+ hasData: categories.length > 0 && series.some((item) => item.points.some((point) => point !== null)),
43
+ };
44
+ }
45
+ transformPie(config, rows) {
46
+ const seriesConfig = config.series[0];
47
+ const categoryField = seriesConfig?.categoryField ?? config.axes?.x?.field;
48
+ if (!seriesConfig || !categoryField || !rows.length) {
49
+ return {
50
+ mode: 'pie',
51
+ categories: [],
52
+ series: [],
53
+ slices: [],
54
+ hasData: false,
55
+ };
56
+ }
57
+ const bucket = new Map();
58
+ for (const row of rows) {
59
+ const key = this.normalizeCategory(row[categoryField]);
60
+ const nextValue = this.extractMetricValue(row, seriesConfig);
61
+ bucket.set(key, (bucket.get(key) ?? 0) + nextValue);
62
+ }
63
+ const slices = Array.from(bucket.entries()).map(([name, value]) => ({
64
+ id: `${seriesConfig.id}:${name}`,
65
+ name,
66
+ value,
67
+ color: seriesConfig.color,
68
+ }));
69
+ return {
70
+ mode: 'pie',
71
+ categories: slices.map((slice) => slice.name),
72
+ series: [],
73
+ slices,
74
+ hasData: slices.some((slice) => slice.value > 0),
75
+ };
76
+ }
77
+ buildCartesianSeries(config, seriesConfig, rows, categories) {
78
+ const categoryField = config.axes?.x?.field;
79
+ const byCategory = new Map();
80
+ for (const row of rows) {
81
+ const key = this.normalizeCategory(row[categoryField]);
82
+ const bucket = byCategory.get(key) ?? [];
83
+ bucket.push(row);
84
+ byCategory.set(key, bucket);
85
+ }
86
+ return {
87
+ id: seriesConfig.id,
88
+ name: seriesConfig.name ?? seriesConfig.metric?.label ?? seriesConfig.id,
89
+ type: config.type === 'bar' || config.type === 'stacked-bar' ? 'bar' : 'line',
90
+ stack: config.type === 'stacked-bar' ? seriesConfig.stackId ?? 'stack-1' : undefined,
91
+ smooth: seriesConfig.smooth ?? config.type === 'area',
92
+ area: config.type === 'area',
93
+ color: seriesConfig.color,
94
+ labelsVisible: seriesConfig.labels?.visible ?? false,
95
+ points: categories.map((category) => {
96
+ const bucket = byCategory.get(category) ?? [];
97
+ if (!bucket.length)
98
+ return null;
99
+ return this.aggregate(bucket, seriesConfig);
100
+ }),
101
+ };
102
+ }
103
+ aggregate(rows, seriesConfig) {
104
+ const aggregation = seriesConfig.metric?.aggregation ?? 'sum';
105
+ if (aggregation === 'count') {
106
+ return rows.length;
107
+ }
108
+ const values = rows
109
+ .map((row) => this.extractMetricValue(row, seriesConfig))
110
+ .filter((value) => Number.isFinite(value));
111
+ if (!values.length)
112
+ return 0;
113
+ switch (aggregation) {
114
+ case 'avg':
115
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
116
+ case 'min':
117
+ return Math.min(...values);
118
+ case 'max':
119
+ return Math.max(...values);
120
+ case 'sum':
121
+ default:
122
+ return values.reduce((sum, value) => sum + value, 0);
123
+ }
124
+ }
125
+ extractMetricValue(row, seriesConfig) {
126
+ const field = seriesConfig.metric?.field;
127
+ if (!field) {
128
+ return 1;
129
+ }
130
+ const value = row[field];
131
+ if (typeof value === 'number')
132
+ return value;
133
+ if (typeof value === 'string') {
134
+ const parsed = Number(value);
135
+ return Number.isFinite(parsed) ? parsed : 0;
136
+ }
137
+ return 0;
138
+ }
139
+ normalizeCategory(value) {
140
+ if (value instanceof Date) {
141
+ return value.toISOString();
142
+ }
143
+ if (value === null || value === undefined || value === '') {
144
+ return 'undefined';
145
+ }
146
+ return String(value);
147
+ }
148
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDataTransformerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
149
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDataTransformerService, providedIn: 'root' });
150
+ }
151
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDataTransformerService, decorators: [{
152
+ type: Injectable,
153
+ args: [{ providedIn: 'root' }]
154
+ }] });
155
+
156
+ class PraxisChartOptionBuilderService {
157
+ transformer;
158
+ constructor(transformer) {
159
+ this.transformer = transformer;
160
+ }
161
+ build(config, rows) {
162
+ const transformed = this.transformer.transform(config, rows);
163
+ const palette = config.theme?.palette;
164
+ const tooltipEnabled = config.theme?.tooltip?.enabled ?? true;
165
+ const legendVisible = config.theme?.legend?.visible ?? true;
166
+ if (transformed.mode === 'pie') {
167
+ const pieSeries = {
168
+ type: 'pie',
169
+ radius: config.type === 'donut' ? ['45%', '70%'] : '70%',
170
+ label: {
171
+ show: config.series[0]?.labels?.visible ?? false,
172
+ },
173
+ data: transformed.slices.map((slice) => ({
174
+ name: slice.name,
175
+ value: slice.value,
176
+ itemStyle: slice.color ? { color: slice.color } : undefined,
177
+ })),
178
+ };
179
+ return {
180
+ backgroundColor: config.theme?.backgroundColor,
181
+ color: palette,
182
+ title: {
183
+ text: this.resolveText(config.title),
184
+ subtext: this.resolveText(config.subtitle),
185
+ left: 'center',
186
+ },
187
+ tooltip: tooltipEnabled ? { trigger: 'item' } : undefined,
188
+ legend: legendVisible
189
+ ? { show: true, orient: 'horizontal', bottom: 0 }
190
+ : { show: false },
191
+ series: [pieSeries],
192
+ };
193
+ }
194
+ return {
195
+ backgroundColor: config.theme?.backgroundColor,
196
+ color: palette,
197
+ title: {
198
+ text: this.resolveText(config.title),
199
+ subtext: this.resolveText(config.subtitle),
200
+ left: 'left',
201
+ },
202
+ tooltip: tooltipEnabled
203
+ ? { trigger: config.theme?.tooltip?.trigger ?? 'axis' }
204
+ : undefined,
205
+ legend: legendVisible
206
+ ? { show: true, top: 0 }
207
+ : { show: false },
208
+ grid: {
209
+ top: 56,
210
+ right: 24,
211
+ bottom: 32,
212
+ left: 48,
213
+ containLabel: true,
214
+ },
215
+ xAxis: {
216
+ type: config.axes?.x?.type ?? 'category',
217
+ name: config.axes?.x?.label,
218
+ data: transformed.categories,
219
+ axisLabel: {
220
+ show: config.axes?.x?.labels?.visible ?? true,
221
+ rotate: config.axes?.x?.labels?.rotate ?? 0,
222
+ },
223
+ },
224
+ yAxis: {
225
+ type: config.axes?.y?.type ?? 'value',
226
+ name: config.axes?.y?.label,
227
+ min: config.axes?.y?.min,
228
+ max: config.axes?.y?.max,
229
+ axisLabel: {
230
+ show: config.axes?.y?.labels?.visible ?? true,
231
+ },
232
+ },
233
+ series: transformed.series.map((series) => ({
234
+ id: series.id,
235
+ name: series.name,
236
+ type: series.type,
237
+ stack: series.stack,
238
+ smooth: series.smooth,
239
+ areaStyle: series.area ? {} : undefined,
240
+ label: { show: series.labelsVisible },
241
+ itemStyle: series.color ? { color: series.color } : undefined,
242
+ data: series.points,
243
+ })),
244
+ };
245
+ }
246
+ resolveText(value) {
247
+ if (!value)
248
+ return undefined;
249
+ if (typeof value === 'string')
250
+ return value;
251
+ if (typeof value === 'object' && value && 'text' in value) {
252
+ const text = value.text;
253
+ return text || undefined;
254
+ }
255
+ return undefined;
256
+ }
257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartOptionBuilderService, deps: [{ token: PraxisChartDataTransformerService }], target: i0.ɵɵFactoryTarget.Injectable });
258
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartOptionBuilderService, providedIn: 'root' });
259
+ }
260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartOptionBuilderService, decorators: [{
261
+ type: Injectable,
262
+ args: [{ providedIn: 'root' }]
263
+ }], ctorParameters: () => [{ type: PraxisChartDataTransformerService }] });
264
+
265
+ use([
266
+ AriaComponent,
267
+ BarChart,
268
+ CanvasRenderer,
269
+ DatasetComponent,
270
+ GridComponent,
271
+ LegendComponent,
272
+ LineChart,
273
+ PieChart,
274
+ TitleComponent,
275
+ TooltipComponent,
276
+ TransformComponent,
277
+ ]);
278
+ class EChartsEngineAdapter {
279
+ optionBuilder;
280
+ chart;
281
+ constructor(optionBuilder) {
282
+ this.optionBuilder = optionBuilder;
283
+ }
284
+ render(host, payload) {
285
+ if (!this.chart) {
286
+ this.chart = init(host);
287
+ }
288
+ const option = this.optionBuilder.build(payload.config, payload.data);
289
+ this.chart?.setOption(option, true);
290
+ this.chart?.off('click');
291
+ this.chart?.on('click', (params) => {
292
+ payload.onPointClick?.({
293
+ chartId: payload.config.id,
294
+ seriesId: params?.seriesId,
295
+ seriesName: params?.seriesName,
296
+ category: params?.name,
297
+ value: params?.value,
298
+ data: params?.data,
299
+ });
300
+ });
301
+ }
302
+ resize() {
303
+ this.chart?.resize();
304
+ }
305
+ destroy() {
306
+ this.chart?.dispose();
307
+ this.chart = undefined;
308
+ }
309
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EChartsEngineAdapter, deps: [{ token: PraxisChartOptionBuilderService }], target: i0.ɵɵFactoryTarget.Injectable });
310
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EChartsEngineAdapter });
311
+ }
312
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EChartsEngineAdapter, decorators: [{
313
+ type: Injectable
314
+ }], ctorParameters: () => [{ type: PraxisChartOptionBuilderService }] });
315
+
316
+ class PraxisChartStatsApiService {
317
+ http;
318
+ apiUrl;
319
+ constructor(http, apiUrl) {
320
+ this.http = http;
321
+ this.apiUrl = apiUrl;
322
+ }
323
+ execute(event, config) {
324
+ const query = event.query;
325
+ const statsRequest = query?.statsRequest;
326
+ if (!query?.statsPath || !statsRequest) {
327
+ return throwError(() => new Error('PraxisChartStatsApiService requires query.statsPath and query.statsRequest for praxis.stats execution.'));
328
+ }
329
+ const categoryField = this.resolveCategoryField(config, statsRequest);
330
+ const metricField = this.resolveMetricField(config);
331
+ const url = this.buildStatsUrl(query.statsPath);
332
+ return this.http
333
+ .post(url, statsRequest)
334
+ .pipe(map((response) => this.toChartRows(response?.data, statsRequest, categoryField, metricField)), catchError((error) => this.handleHttpError(error)));
335
+ }
336
+ toChartRows(response, request, categoryField, metricField) {
337
+ if (!response) {
338
+ return [];
339
+ }
340
+ if ('points' in response) {
341
+ return response.points.map((point) => {
342
+ const category = point.label ?? point.start ?? point.end ?? '';
343
+ const metricValue = this.resolveMetricValue(point.value, point.count);
344
+ return {
345
+ [categoryField]: category,
346
+ [metricField]: metricValue,
347
+ key: point.start ?? point.label ?? point.end ?? category,
348
+ label: point.label ?? category,
349
+ value: point.value ?? null,
350
+ count: point.count ?? null,
351
+ start: point.start ?? null,
352
+ end: point.end ?? null,
353
+ granularity: response.granularity ?? null,
354
+ };
355
+ });
356
+ }
357
+ if ('buckets' in response) {
358
+ return response.buckets.map((bucket) => {
359
+ const category = this.resolveBucketCategory(bucket);
360
+ const metricValue = this.resolveMetricValue(bucket.value, bucket.count);
361
+ return {
362
+ [categoryField]: category,
363
+ [metricField]: metricValue,
364
+ key: bucket.key ?? null,
365
+ label: bucket.label ?? category,
366
+ value: bucket.value ?? null,
367
+ count: bucket.count ?? null,
368
+ from: bucket.from ?? null,
369
+ to: bucket.to ?? null,
370
+ mode: 'mode' in response ? response.mode ?? null : null,
371
+ requestMode: 'mode' in request ? request.mode : null,
372
+ };
373
+ });
374
+ }
375
+ return [];
376
+ }
377
+ resolveBucketCategory(bucket) {
378
+ if (bucket.label !== null && bucket.label !== undefined && bucket.label !== '') {
379
+ return String(bucket.label);
380
+ }
381
+ if (bucket.key !== null && bucket.key !== undefined && bucket.key !== '') {
382
+ return String(bucket.key);
383
+ }
384
+ if (bucket.from !== null || bucket.to !== null) {
385
+ return `${bucket.from ?? ''} - ${bucket.to ?? ''}`.trim();
386
+ }
387
+ return '';
388
+ }
389
+ resolveMetricValue(value, count) {
390
+ if (typeof value === 'number' && Number.isFinite(value)) {
391
+ return value;
392
+ }
393
+ if (typeof count === 'number' && Number.isFinite(count)) {
394
+ return count;
395
+ }
396
+ return 0;
397
+ }
398
+ resolveCategoryField(config, request) {
399
+ const firstSeries = config.series[0];
400
+ return (firstSeries?.categoryField
401
+ || config.axes?.x?.field
402
+ || request.field
403
+ || 'category');
404
+ }
405
+ resolveMetricField(config) {
406
+ return config.series[0]?.metric?.field || 'value';
407
+ }
408
+ buildStatsUrl(statsPath) {
409
+ const base = this.buildDefaultApiBase();
410
+ const normalizedStatsPath = this.normalizePath(statsPath);
411
+ const resolvedStatsPath = this.shouldStripLeadingApiSegment(base)
412
+ ? this.stripLeadingApiSegment(normalizedStatsPath)
413
+ : normalizedStatsPath;
414
+ return `${base}/${resolvedStatsPath}`;
415
+ }
416
+ buildDefaultApiBase() {
417
+ const entry = this.apiUrl?.default ?? {};
418
+ return buildApiUrl(entry).replace(/\/+$/, '');
419
+ }
420
+ normalizePath(value) {
421
+ return String(value || '').trim().replace(/^\/+|\/+$/g, '');
422
+ }
423
+ stripLeadingApiSegment(value) {
424
+ return value.replace(/^api\/+/i, '');
425
+ }
426
+ shouldStripLeadingApiSegment(base) {
427
+ return /\/api$/i.test(base);
428
+ }
429
+ handleHttpError(error) {
430
+ if (error instanceof HttpErrorResponse) {
431
+ const detail = typeof error.message === 'string' && error.message.trim()
432
+ ? error.message
433
+ : `HTTP ${error.status || 0}`;
434
+ return throwError(() => new Error(detail));
435
+ }
436
+ return throwError(() => error instanceof Error ? error : new Error('Unexpected failure during praxis.stats execution.'));
437
+ }
438
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, deps: [{ token: i1.HttpClient }, { token: API_URL }], target: i0.ɵɵFactoryTarget.Injectable });
439
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, providedIn: 'root' });
440
+ }
441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, decorators: [{
442
+ type: Injectable,
443
+ args: [{ providedIn: 'root' }]
444
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
445
+ type: Inject,
446
+ args: [API_URL]
447
+ }] }] });
448
+
449
+ const PRAXIS_CHART_ENGINE = new InjectionToken('PRAXIS_CHART_ENGINE');
450
+
451
+ class PraxisChartComponent {
452
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
453
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
454
+ pointClick = output();
455
+ queryRequest = output();
456
+ loadStateChange = output();
457
+ chartHost = viewChild('chartHost', ...(ngDevMode ? [{ debugName: "chartHost" }] : []));
458
+ engine = inject(PRAXIS_CHART_ENGINE);
459
+ transformer = inject(PraxisChartDataTransformerService);
460
+ statsApi = inject(PraxisChartStatsApiService);
461
+ i18n = inject(PraxisI18nService);
462
+ destroyRef = inject(DestroyRef);
463
+ resizeObserver = signal(null, ...(ngDevMode ? [{ debugName: "resizeObserver" }] : []));
464
+ currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : []));
465
+ remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : []));
466
+ remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
467
+ remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
468
+ previousRemoteSignature = null;
469
+ resolvedData = computed(() => {
470
+ const inputData = this.data();
471
+ if (inputData !== null && inputData !== undefined) {
472
+ return inputData;
473
+ }
474
+ const remoteData = this.remoteResolvedData();
475
+ if (remoteData !== null) {
476
+ return remoteData;
477
+ }
478
+ const dataSource = this.config().dataSource;
479
+ if (dataSource?.kind === 'local') {
480
+ return dataSource.items ?? [];
481
+ }
482
+ return [];
483
+ }, ...(ngDevMode ? [{ debugName: "resolvedData" }] : []));
484
+ resolvedHeight = computed(() => {
485
+ const value = this.config().height;
486
+ if (typeof value === 'number')
487
+ return `${value}px`;
488
+ return value || '320px';
489
+ }, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : []));
490
+ renderConfig = computed(() => {
491
+ const config = this.config();
492
+ const explicitData = this.data();
493
+ const remoteState = this.remoteRuntimeState();
494
+ const remoteData = this.remoteResolvedData();
495
+ if (explicitData !== null && explicitData !== undefined) {
496
+ return config;
497
+ }
498
+ if (config.dataSource?.kind !== 'remote') {
499
+ return config;
500
+ }
501
+ if (remoteState === 'loading') {
502
+ return {
503
+ ...config,
504
+ preferredLoadState: 'loading',
505
+ };
506
+ }
507
+ if (remoteState === 'error') {
508
+ return {
509
+ ...config,
510
+ preferredLoadState: 'error',
511
+ state: config.state?.error
512
+ ? {
513
+ ...config.state,
514
+ error: {
515
+ ...config.state.error,
516
+ technicalDetails: this.remoteTechnicalError() ?? config.state.error.technicalDetails,
517
+ },
518
+ }
519
+ : config.state,
520
+ };
521
+ }
522
+ if (remoteData !== null) {
523
+ return {
524
+ ...config,
525
+ preferredLoadState: undefined,
526
+ series: config.series.map((series) => ({
527
+ ...series,
528
+ metric: {
529
+ ...series.metric,
530
+ aggregation: 'sum',
531
+ field: series.metric?.field || 'value',
532
+ },
533
+ })),
534
+ };
535
+ }
536
+ return config;
537
+ }, ...(ngDevMode ? [{ debugName: "renderConfig" }] : []));
538
+ loadingLabel = computed(() => this.i18n.resolve(this.renderConfig().state?.loadingLabel), ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
539
+ emptyTitle = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.title), ...(ngDevMode ? [{ debugName: "emptyTitle" }] : []));
540
+ emptyDescription = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.description), ...(ngDevMode ? [{ debugName: "emptyDescription" }] : []));
541
+ errorTitle = computed(() => this.i18n.resolve(this.renderConfig().state?.error?.title), ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
542
+ errorDescription = computed(() => this.i18n.resolve(this.renderConfig().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
543
+ loadState = computed(() => {
544
+ const config = this.renderConfig();
545
+ if (config.preferredLoadState === 'loading')
546
+ return 'loading';
547
+ if (config.preferredLoadState === 'error')
548
+ return 'error';
549
+ const dataSource = config.dataSource;
550
+ const explicitData = this.data();
551
+ if (dataSource?.kind === 'remote' && (explicitData === null || explicitData === undefined)) {
552
+ return this.remoteRuntimeState() === 'ready' ? 'ready' : 'loading';
553
+ }
554
+ const transformed = this.transformer.transform(config, this.resolvedData());
555
+ return transformed.hasData ? 'ready' : 'empty';
556
+ }, ...(ngDevMode ? [{ debugName: "loadState" }] : []));
557
+ constructor() {
558
+ effect(() => {
559
+ const config = this.config();
560
+ const explicitData = this.data();
561
+ const nextSignature = explicitData === null || explicitData === undefined
562
+ ? this.buildRemoteSignature(config)
563
+ : null;
564
+ if (nextSignature === this.previousRemoteSignature) {
565
+ return;
566
+ }
567
+ this.previousRemoteSignature = nextSignature;
568
+ this.remoteResolvedData.set(null);
569
+ this.remoteRuntimeState.set('idle');
570
+ this.remoteTechnicalError.set(null);
571
+ });
572
+ effect(() => {
573
+ const nextState = this.loadState();
574
+ if (this.currentLoadState() !== nextState) {
575
+ this.currentLoadState.set(nextState);
576
+ this.loadStateChange.emit(nextState);
577
+ }
578
+ });
579
+ effect(() => {
580
+ const config = this.config();
581
+ const explicitData = this.data();
582
+ if (config.dataSource?.kind !== 'remote' || (explicitData !== null && explicitData !== undefined)) {
583
+ return;
584
+ }
585
+ if (this.remoteRuntimeState() === 'loading' || this.remoteResolvedData() !== null) {
586
+ return;
587
+ }
588
+ const event = {
589
+ chartId: config.id,
590
+ dataSource: config.dataSource,
591
+ query: config.dataSource.query,
592
+ };
593
+ this.queryRequest.emit(event);
594
+ this.remoteRuntimeState.set('loading');
595
+ this.remoteResolvedData.set([]);
596
+ this.remoteTechnicalError.set(null);
597
+ this.statsApi.execute(event, config)
598
+ .pipe(takeUntilDestroyed(this.destroyRef))
599
+ .subscribe({
600
+ next: (rows) => {
601
+ this.remoteResolvedData.set(rows);
602
+ this.remoteRuntimeState.set('ready');
603
+ this.remoteTechnicalError.set(null);
604
+ },
605
+ error: (error) => {
606
+ this.remoteResolvedData.set([]);
607
+ this.remoteRuntimeState.set('error');
608
+ this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
609
+ ? error.message
610
+ : 'praxis.stats request failed');
611
+ },
612
+ });
613
+ });
614
+ effect(() => {
615
+ if (this.loadState() !== 'ready') {
616
+ this.engine.destroy();
617
+ return;
618
+ }
619
+ const host = this.chartHost()?.nativeElement;
620
+ if (!host)
621
+ return;
622
+ this.engine.render(host, {
623
+ config: this.renderConfig(),
624
+ data: this.resolvedData(),
625
+ onPointClick: (event) => this.pointClick.emit(event),
626
+ });
627
+ this.ensureResizeObserver(host);
628
+ });
629
+ this.destroyRef.onDestroy(() => {
630
+ this.resizeObserver()?.disconnect();
631
+ this.engine.destroy();
632
+ });
633
+ }
634
+ ensureResizeObserver(host) {
635
+ if (this.resizeObserver()) {
636
+ return;
637
+ }
638
+ const observer = new ResizeObserver(() => {
639
+ this.engine.resize();
640
+ });
641
+ observer.observe(host);
642
+ this.resizeObserver.set(observer);
643
+ }
644
+ buildRemoteSignature(config) {
645
+ if (config.dataSource?.kind !== 'remote') {
646
+ return null;
647
+ }
648
+ return JSON.stringify({
649
+ id: config.id,
650
+ resourcePath: config.dataSource.resourcePath,
651
+ query: config.dataSource.query,
652
+ });
653
+ }
654
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
655
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartComponent, isStandalone: true, selector: "praxis-chart", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", queryRequest: "queryRequest", loadStateChange: "loadStateChange" }, providers: [
656
+ EChartsEngineAdapter,
657
+ {
658
+ provide: PRAXIS_CHART_ENGINE,
659
+ useExisting: EChartsEngineAdapter,
660
+ },
661
+ ], viewQueries: [{ propertyName: "chartHost", first: true, predicate: ["chartHost"], descendants: true, isSignal: true }], ngImport: i0, template: `
662
+ <section class="praxis-chart-shell" [style.height]="resolvedHeight()">
663
+ @if (loadState() === 'loading') {
664
+ <div class="praxis-chart-state praxis-chart-state-loading">
665
+ <div class="praxis-chart-spinner" aria-hidden="true"></div>
666
+ @if (loadingLabel()) {
667
+ <div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
668
+ }
669
+ </div>
670
+ } @else if (loadState() === 'error') {
671
+ <div class="praxis-chart-state praxis-chart-state-error">
672
+ @if (errorTitle()) {
673
+ <div class="praxis-chart-state-title">{{ errorTitle() }}</div>
674
+ }
675
+ @if (errorDescription()) {
676
+ <div class="praxis-chart-state-description">{{ errorDescription() }}</div>
677
+ }
678
+ </div>
679
+ } @else if (loadState() === 'empty') {
680
+ <div class="praxis-chart-state praxis-chart-state-empty">
681
+ @if (emptyTitle()) {
682
+ <div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
683
+ }
684
+ @if (emptyDescription()) {
685
+ <div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
686
+ }
687
+ </div>
688
+ } @else {
689
+ <div #chartHost class="praxis-chart-host"></div>
690
+ }
691
+ </section>
692
+ `, isInline: true, styles: [":host{display:block;min-width:0}.praxis-chart-shell{position:relative;width:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
693
+ }
694
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartComponent, decorators: [{
695
+ type: Component,
696
+ args: [{ selector: 'praxis-chart', standalone: true, imports: [CommonModule], providers: [
697
+ EChartsEngineAdapter,
698
+ {
699
+ provide: PRAXIS_CHART_ENGINE,
700
+ useExisting: EChartsEngineAdapter,
701
+ },
702
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
703
+ <section class="praxis-chart-shell" [style.height]="resolvedHeight()">
704
+ @if (loadState() === 'loading') {
705
+ <div class="praxis-chart-state praxis-chart-state-loading">
706
+ <div class="praxis-chart-spinner" aria-hidden="true"></div>
707
+ @if (loadingLabel()) {
708
+ <div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
709
+ }
710
+ </div>
711
+ } @else if (loadState() === 'error') {
712
+ <div class="praxis-chart-state praxis-chart-state-error">
713
+ @if (errorTitle()) {
714
+ <div class="praxis-chart-state-title">{{ errorTitle() }}</div>
715
+ }
716
+ @if (errorDescription()) {
717
+ <div class="praxis-chart-state-description">{{ errorDescription() }}</div>
718
+ }
719
+ </div>
720
+ } @else if (loadState() === 'empty') {
721
+ <div class="praxis-chart-state praxis-chart-state-empty">
722
+ @if (emptyTitle()) {
723
+ <div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
724
+ }
725
+ @if (emptyDescription()) {
726
+ <div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
727
+ }
728
+ </div>
729
+ } @else {
730
+ <div #chartHost class="praxis-chart-host"></div>
731
+ }
732
+ </section>
733
+ `, styles: [":host{display:block;min-width:0}.praxis-chart-shell{position:relative;width:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
734
+ }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }], chartHost: [{ type: i0.ViewChild, args: ['chartHost', { isSignal: true }] }] } });
735
+
736
+ const PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH = {
737
+ Jan: [
738
+ { segment: 'Enterprise', total: 52000 },
739
+ { segment: 'Mid-market', total: 38000 },
740
+ { segment: 'SMB', total: 30000 },
741
+ ],
742
+ Fev: [
743
+ { segment: 'Enterprise', total: 61000 },
744
+ { segment: 'Mid-market', total: 42000 },
745
+ { segment: 'SMB', total: 35000 },
746
+ ],
747
+ Mar: [
748
+ { segment: 'Enterprise', total: 68000 },
749
+ { segment: 'Mid-market', total: 47000 },
750
+ { segment: 'SMB', total: 36000 },
751
+ ],
752
+ Abr: [
753
+ { segment: 'Enterprise', total: 64000 },
754
+ { segment: 'Mid-market', total: 49500 },
755
+ { segment: 'SMB', total: 36000 },
756
+ ],
757
+ };
758
+
759
+ class PraxisChartDrilldownPanelComponent {
760
+ title = input('Detalhamento por segmento', ...(ngDevMode ? [{ debugName: "title" }] : []));
761
+ selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
762
+ activeCategory = computed(() => {
763
+ const raw = this.selection()?.category;
764
+ return typeof raw === 'string' ? raw : null;
765
+ }, ...(ngDevMode ? [{ debugName: "activeCategory" }] : []));
766
+ detailData = computed(() => {
767
+ const category = this.activeCategory();
768
+ if (!category)
769
+ return [];
770
+ return PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH[category] ?? [];
771
+ }, ...(ngDevMode ? [{ debugName: "detailData" }] : []));
772
+ detailChartConfig = computed(() => {
773
+ const category = this.activeCategory();
774
+ return {
775
+ id: 'chart-drilldown-detail',
776
+ type: 'donut',
777
+ title: { text: category ? `Mix de receita em ${category}` : 'Aguardando selecao' },
778
+ subtitle: { text: category ? 'Drill-down local com JSON mockado' : 'Selecione um ponto no chart principal' },
779
+ height: 280,
780
+ series: [
781
+ {
782
+ id: 'segmentMix',
783
+ categoryField: 'segment',
784
+ metric: { field: 'total', aggregation: 'sum' },
785
+ labels: { visible: true },
786
+ },
787
+ ],
788
+ emptyState: {
789
+ title: { text: 'Nenhum recorte selecionado' },
790
+ description: { text: 'O painel de drill-down usa mocks locais e reage ao pointClick do chart principal.' },
791
+ },
792
+ theme: {
793
+ palette: ['#1263b4', '#2b8a3e', '#f08c00', '#7b61ff'],
794
+ },
795
+ };
796
+ }, ...(ngDevMode ? [{ debugName: "detailChartConfig" }] : []));
797
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDrilldownPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
798
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartDrilldownPanelComponent, isStandalone: true, selector: "praxis-chart-drilldown-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
799
+ <section class="drilldown-shell">
800
+ <header class="drilldown-header">
801
+ <p class="drilldown-eyebrow">Drill-down local</p>
802
+ <h3>{{ title() }}</h3>
803
+ @if (activeCategory()) {
804
+ <p class="drilldown-description">Recorte atual: {{ activeCategory() }}</p>
805
+ } @else {
806
+ <p class="drilldown-description">Clique em uma barra do chart principal para abrir o detalhamento local.</p>
807
+ }
808
+ </header>
809
+
810
+ <praxis-chart
811
+ [config]="detailChartConfig()"
812
+ [data]="detailData()"
813
+ ></praxis-chart>
814
+ </section>
815
+ `, isInline: true, styles: [":host{display:block}.drilldown-shell{display:grid;gap:14px}.drilldown-header h3,.drilldown-eyebrow,.drilldown-description{margin:0}.drilldown-eyebrow{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.drilldown-header h3{font-size:1.2rem;color:var(--md-sys-color-on-surface, #1a1b20)}.drilldown-description{color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data"], outputs: ["pointClick", "queryRequest", "loadStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
816
+ }
817
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDrilldownPanelComponent, decorators: [{
818
+ type: Component,
819
+ args: [{ selector: 'praxis-chart-drilldown-panel', standalone: true, imports: [CommonModule, PraxisChartComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
820
+ <section class="drilldown-shell">
821
+ <header class="drilldown-header">
822
+ <p class="drilldown-eyebrow">Drill-down local</p>
823
+ <h3>{{ title() }}</h3>
824
+ @if (activeCategory()) {
825
+ <p class="drilldown-description">Recorte atual: {{ activeCategory() }}</p>
826
+ } @else {
827
+ <p class="drilldown-description">Clique em uma barra do chart principal para abrir o detalhamento local.</p>
828
+ }
829
+ </header>
830
+
831
+ <praxis-chart
832
+ [config]="detailChartConfig()"
833
+ [data]="detailData()"
834
+ ></praxis-chart>
835
+ </section>
836
+ `, styles: [":host{display:block}.drilldown-shell{display:grid;gap:14px}.drilldown-header h3,.drilldown-eyebrow,.drilldown-description{margin:0}.drilldown-eyebrow{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.drilldown-header h3{font-size:1.2rem;color:var(--md-sys-color-on-surface, #1a1b20)}.drilldown-description{color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"] }]
837
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }] } });
838
+
839
+ class PraxisChartStateProbeComponent {
840
+ title = input('Chart Runtime Probe', ...(ngDevMode ? [{ debugName: "title" }] : []));
841
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
842
+ serializedValue = computed(() => JSON.stringify(this.value(), null, 2), ...(ngDevMode ? [{ debugName: "serializedValue" }] : []));
843
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStateProbeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
844
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartStateProbeComponent, isStandalone: true, selector: "praxis-chart-state-probe", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
845
+ <section class="probe-shell">
846
+ <header class="probe-header">
847
+ <p class="probe-eyebrow">Probe</p>
848
+ <h3>{{ title() }}</h3>
849
+ </header>
850
+
851
+ @if (value() === null || value() === undefined || value() === '') {
852
+ <div class="probe-empty">Aguardando eventos do runtime.</div>
853
+ } @else {
854
+ <pre>{{ serializedValue() }}</pre>
855
+ }
856
+ </section>
857
+ `, isInline: true, styles: [":host{display:block}.probe-shell{display:grid;gap:12px;min-height:220px;padding:18px;border-radius:20px;background:linear-gradient(180deg,#0b111ff5,#0b111fe0),radial-gradient(circle at top right,rgba(18,99,180,.32),transparent 35%);color:#d7e6ff}.probe-header h3,.probe-eyebrow{margin:0}.probe-eyebrow{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:#d7e6ffb3}.probe-empty{color:#d7e6ffc7}pre{margin:0;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:.84rem;line-height:1.45}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
858
+ }
859
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStateProbeComponent, decorators: [{
860
+ type: Component,
861
+ args: [{ selector: 'praxis-chart-state-probe', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
862
+ <section class="probe-shell">
863
+ <header class="probe-header">
864
+ <p class="probe-eyebrow">Probe</p>
865
+ <h3>{{ title() }}</h3>
866
+ </header>
867
+
868
+ @if (value() === null || value() === undefined || value() === '') {
869
+ <div class="probe-empty">Aguardando eventos do runtime.</div>
870
+ } @else {
871
+ <pre>{{ serializedValue() }}</pre>
872
+ }
873
+ </section>
874
+ `, styles: [":host{display:block}.probe-shell{display:grid;gap:12px;min-height:220px;padding:18px;border-radius:20px;background:linear-gradient(180deg,#0b111ff5,#0b111fe0),radial-gradient(circle at top right,rgba(18,99,180,.32),transparent 35%);color:#d7e6ff}.probe-header h3,.probe-eyebrow{margin:0}.probe-eyebrow{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:#d7e6ffb3}.probe-empty{color:#d7e6ffc7}pre{margin:0;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:.84rem;line-height:1.45}\n"] }]
875
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
876
+
877
+ const PRAXIS_CHART_COMPONENT_METADATA = {
878
+ id: 'praxis-chart',
879
+ componentType: 'praxis-chart',
880
+ displayName: 'Praxis Chart',
881
+ selector: 'praxis-chart',
882
+ component: PraxisChartComponent,
883
+ friendlyName: 'Praxis Chart',
884
+ description: 'Chart component for analytics and metadata-driven visualizations in Praxis UI.',
885
+ icon: 'bar_chart',
886
+ tags: ['chart', 'analytics', 'widget', 'visualization'],
887
+ lib: '@praxisui/charts',
888
+ inputs: [
889
+ {
890
+ name: 'config',
891
+ type: 'PraxisChartConfig',
892
+ description: 'Declarative chart configuration with metadata-oriented semantics.',
893
+ },
894
+ {
895
+ name: 'data',
896
+ type: 'PraxisChartDataRow[]',
897
+ description: 'Optional local dataset that takes precedence over local datasource items.',
898
+ },
899
+ ],
900
+ outputs: [
901
+ {
902
+ name: 'pointClick',
903
+ type: 'PraxisChartPointEvent',
904
+ description: 'Emitted when the host wants to react to a point/series click.',
905
+ },
906
+ {
907
+ name: 'queryRequest',
908
+ type: 'PraxisChartQueryRequestEvent',
909
+ description: 'Emitted before a remote praxis.stats datasource is resolved, allowing host-side observability or overrides.',
910
+ },
911
+ {
912
+ name: 'loadStateChange',
913
+ type: 'PraxisChartLoadState',
914
+ description: 'Emitted when the chart state changes.',
915
+ },
916
+ ],
917
+ };
918
+ function providePraxisChartsMetadata() {
919
+ return {
920
+ provide: ENVIRONMENT_INITIALIZER,
921
+ multi: true,
922
+ useFactory: (registry) => () => {
923
+ registry.register(PRAXIS_CHART_COMPONENT_METADATA);
924
+ },
925
+ deps: [ComponentMetadataRegistry],
926
+ };
927
+ }
928
+
929
+ const PRAXIS_CHART_DRILLDOWN_PANEL_METADATA = {
930
+ id: 'praxis-chart-drilldown-panel',
931
+ componentType: 'praxis-chart-drilldown-panel',
932
+ displayName: 'Praxis Chart Drilldown Panel',
933
+ selector: 'praxis-chart-drilldown-panel',
934
+ component: PraxisChartDrilldownPanelComponent,
935
+ friendlyName: 'Praxis Chart Drilldown Panel',
936
+ description: 'Local drill-down panel that consumes chart point events and renders a derived detail chart.',
937
+ icon: 'query_stats',
938
+ tags: ['chart', 'drilldown', 'widget', 'analytics'],
939
+ lib: '@praxisui/charts',
940
+ inputs: [
941
+ {
942
+ name: 'title',
943
+ type: 'string',
944
+ description: 'Panel title for the drill-down view.',
945
+ },
946
+ {
947
+ name: 'selection',
948
+ type: 'PraxisChartPointEvent',
949
+ description: 'Event emitted by a source chart and used to resolve local detail datasets.',
950
+ },
951
+ ],
952
+ };
953
+ function providePraxisChartDrilldownPanelMetadata() {
954
+ return {
955
+ provide: ENVIRONMENT_INITIALIZER,
956
+ multi: true,
957
+ useFactory: (registry) => () => {
958
+ registry.register(PRAXIS_CHART_DRILLDOWN_PANEL_METADATA);
959
+ },
960
+ deps: [ComponentMetadataRegistry],
961
+ };
962
+ }
963
+
964
+ const PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA = {
965
+ id: 'praxis-chart-state-probe',
966
+ componentType: 'praxis-chart-state-probe',
967
+ displayName: 'Praxis Chart State Probe',
968
+ selector: 'praxis-chart-state-probe',
969
+ component: PraxisChartStateProbeComponent,
970
+ friendlyName: 'Praxis Chart State Probe',
971
+ description: 'Diagnostic widget used to inspect chart events and runtime payloads during local validation.',
972
+ icon: 'monitoring',
973
+ tags: ['chart', 'probe', 'debug', 'widget'],
974
+ lib: '@praxisui/charts',
975
+ inputs: [
976
+ {
977
+ name: 'title',
978
+ type: 'string',
979
+ description: 'Probe panel title.',
980
+ },
981
+ {
982
+ name: 'value',
983
+ type: 'unknown',
984
+ description: 'Payload rendered as formatted JSON.',
985
+ },
986
+ ],
987
+ };
988
+ function providePraxisChartStateProbeMetadata() {
989
+ return {
990
+ provide: ENVIRONMENT_INITIALIZER,
991
+ multi: true,
992
+ useFactory: (registry) => () => {
993
+ registry.register(PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA);
994
+ },
995
+ deps: [ComponentMetadataRegistry],
996
+ };
997
+ }
998
+
999
+ class PraxisChartShowcaseWidgetComponent {
1000
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
1001
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
1002
+ viewMode = input('chart', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
1003
+ pointClick = output();
1004
+ queryRequest = output();
1005
+ loadStateChange = output();
1006
+ statsApi = inject(PraxisChartStatsApiService);
1007
+ destroyRef = inject(DestroyRef);
1008
+ remoteResolvedRows = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedRows" }] : []));
1009
+ remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
1010
+ remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
1011
+ previousRemoteSignature = null;
1012
+ resolvedRows = computed(() => {
1013
+ const explicitData = this.data();
1014
+ if (explicitData !== null && explicitData !== undefined) {
1015
+ return explicitData;
1016
+ }
1017
+ return this.remoteResolvedRows() ?? [];
1018
+ }, ...(ngDevMode ? [{ debugName: "resolvedRows" }] : []));
1019
+ tableId = computed(() => `${this.config().id || 'praxis-chart'}-showcase-table`, ...(ngDevMode ? [{ debugName: "tableId" }] : []));
1020
+ resolvedTitle = computed(() => resolveText(this.config().title) || 'Tabela analitica', ...(ngDevMode ? [{ debugName: "resolvedTitle" }] : []));
1021
+ resolvedSubtitle = computed(() => resolveText(this.config().subtitle) || 'Mesmo dataset em visualizacao tabular', ...(ngDevMode ? [{ debugName: "resolvedSubtitle" }] : []));
1022
+ loadingLabel = computed(() => resolveText(this.config().state?.loadingLabel) || 'Carregando analytics...', ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
1023
+ errorTitle = computed(() => this.remoteTechnicalError()
1024
+ || resolveText(this.config().state?.error?.title)
1025
+ || 'Falha no contrato analitico', ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
1026
+ errorDescription = computed(() => resolveText(this.config().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
1027
+ stateMode = computed(() => resolveShowcaseStateMode(this.config(), this.remoteRuntimeState()), ...(ngDevMode ? [{ debugName: "stateMode" }] : []));
1028
+ tableConfig = computed(() => buildTableConfig(this.config(), this.resolvedRows()), ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
1029
+ constructor() {
1030
+ effect(() => {
1031
+ const config = this.config();
1032
+ const explicitData = this.data();
1033
+ const nextSignature = explicitData === null || explicitData === undefined
1034
+ ? buildRemoteSignature(config)
1035
+ : null;
1036
+ if (nextSignature === this.previousRemoteSignature) {
1037
+ return;
1038
+ }
1039
+ this.previousRemoteSignature = nextSignature;
1040
+ this.remoteResolvedRows.set(null);
1041
+ this.remoteRuntimeState.set('idle');
1042
+ this.remoteTechnicalError.set(null);
1043
+ });
1044
+ effect(() => {
1045
+ const config = this.config();
1046
+ const explicitData = this.data();
1047
+ if (this.viewMode() !== 'table') {
1048
+ return;
1049
+ }
1050
+ if (config.dataSource?.kind !== 'remote' || (explicitData !== null && explicitData !== undefined)) {
1051
+ return;
1052
+ }
1053
+ if (this.remoteRuntimeState() === 'loading' || this.remoteResolvedRows() !== null) {
1054
+ return;
1055
+ }
1056
+ const event = {
1057
+ chartId: config.id,
1058
+ dataSource: config.dataSource,
1059
+ query: config.dataSource.query,
1060
+ };
1061
+ this.queryRequest.emit(event);
1062
+ this.remoteRuntimeState.set('loading');
1063
+ this.remoteResolvedRows.set([]);
1064
+ this.remoteTechnicalError.set(null);
1065
+ this.statsApi.execute(event, config)
1066
+ .pipe(takeUntilDestroyed(this.destroyRef))
1067
+ .subscribe({
1068
+ next: (rows) => {
1069
+ this.remoteResolvedRows.set(rows);
1070
+ this.remoteRuntimeState.set('ready');
1071
+ this.remoteTechnicalError.set(null);
1072
+ },
1073
+ error: (error) => {
1074
+ this.remoteResolvedRows.set([]);
1075
+ this.remoteRuntimeState.set('error');
1076
+ this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
1077
+ ? error.message
1078
+ : 'praxis.stats request failed');
1079
+ },
1080
+ });
1081
+ });
1082
+ }
1083
+ handleRowClick(event) {
1084
+ const row = event?.row;
1085
+ if (!row) {
1086
+ return;
1087
+ }
1088
+ const config = this.config();
1089
+ const primarySeries = config.series[0];
1090
+ const categoryField = primarySeries?.categoryField || config.axes?.x?.field;
1091
+ const metricField = primarySeries?.metric?.field;
1092
+ this.pointClick.emit({
1093
+ chartId: config.id,
1094
+ seriesId: primarySeries?.id,
1095
+ seriesName: primarySeries?.name,
1096
+ category: categoryField ? stringifyCell(row[categoryField]) : undefined,
1097
+ value: metricField ? row[metricField] : undefined,
1098
+ data: row,
1099
+ });
1100
+ }
1101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1102
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartShowcaseWidgetComponent, isStandalone: true, selector: "praxis-chart-showcase-widget", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", queryRequest: "queryRequest", loadStateChange: "loadStateChange" }, ngImport: i0, template: `
1103
+ @if (viewMode() === 'chart') {
1104
+ <praxis-chart
1105
+ [config]="config()"
1106
+ [data]="data()"
1107
+ (pointClick)="pointClick.emit($event)"
1108
+ (queryRequest)="queryRequest.emit($event)"
1109
+ (loadStateChange)="loadStateChange.emit($event)"
1110
+ ></praxis-chart>
1111
+ } @else if (stateMode() === 'loading') {
1112
+ <section class="showcase-state-card">
1113
+ <h4>{{ loadingLabel() }}</h4>
1114
+ </section>
1115
+ } @else if (stateMode() === 'error') {
1116
+ <section class="showcase-state-card showcase-state-card-error">
1117
+ <h4>{{ errorTitle() }}</h4>
1118
+ @if (errorDescription()) {
1119
+ <p>{{ errorDescription() }}</p>
1120
+ }
1121
+ </section>
1122
+ } @else {
1123
+ <praxis-table
1124
+ [config]="tableConfig()"
1125
+ [data]="resolvedRows()"
1126
+ [tableId]="tableId()"
1127
+ [title]="resolvedTitle()"
1128
+ [subtitle]="resolvedSubtitle()"
1129
+ [icon]="'table_view'"
1130
+ (rowClick)="handleRowClick($event)"
1131
+ ></praxis-table>
1132
+ }
1133
+ `, isInline: true, styles: [":host{display:block;min-width:0}.showcase-state-card{min-height:240px;display:grid;place-content:center;gap:8px;padding:24px;border-radius:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#fffffff5,#f4f7fbfa);text-align:center}.showcase-state-card h4{margin:0;font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.showcase-state-card p{margin:0;color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data"], outputs: ["pointClick", "queryRequest", "loadStateChange"] }, { kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "dslFunctionRegistry", "editModeEnabled", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "metadataChange", "loadingStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1134
+ }
1135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, decorators: [{
1136
+ type: Component,
1137
+ args: [{ selector: 'praxis-chart-showcase-widget', standalone: true, imports: [CommonModule, PraxisChartComponent, PraxisTable], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1138
+ @if (viewMode() === 'chart') {
1139
+ <praxis-chart
1140
+ [config]="config()"
1141
+ [data]="data()"
1142
+ (pointClick)="pointClick.emit($event)"
1143
+ (queryRequest)="queryRequest.emit($event)"
1144
+ (loadStateChange)="loadStateChange.emit($event)"
1145
+ ></praxis-chart>
1146
+ } @else if (stateMode() === 'loading') {
1147
+ <section class="showcase-state-card">
1148
+ <h4>{{ loadingLabel() }}</h4>
1149
+ </section>
1150
+ } @else if (stateMode() === 'error') {
1151
+ <section class="showcase-state-card showcase-state-card-error">
1152
+ <h4>{{ errorTitle() }}</h4>
1153
+ @if (errorDescription()) {
1154
+ <p>{{ errorDescription() }}</p>
1155
+ }
1156
+ </section>
1157
+ } @else {
1158
+ <praxis-table
1159
+ [config]="tableConfig()"
1160
+ [data]="resolvedRows()"
1161
+ [tableId]="tableId()"
1162
+ [title]="resolvedTitle()"
1163
+ [subtitle]="resolvedSubtitle()"
1164
+ [icon]="'table_view'"
1165
+ (rowClick)="handleRowClick($event)"
1166
+ ></praxis-table>
1167
+ }
1168
+ `, styles: [":host{display:block;min-width:0}.showcase-state-card{min-height:240px;display:grid;place-content:center;gap:8px;padding:24px;border-radius:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#fffffff5,#f4f7fbfa);text-align:center}.showcase-state-card h4{margin:0;font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.showcase-state-card p{margin:0;color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"] }]
1169
+ }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], viewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewMode", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }] } });
1170
+ function resolveShowcaseStateMode(config, remoteRuntimeState = 'idle') {
1171
+ if (config.preferredLoadState === 'loading') {
1172
+ return 'loading';
1173
+ }
1174
+ if (config.preferredLoadState === 'error') {
1175
+ return 'error';
1176
+ }
1177
+ if (config.dataSource?.kind === 'remote' && remoteRuntimeState === 'error') {
1178
+ return 'error';
1179
+ }
1180
+ if (config.dataSource?.kind === 'remote' && remoteRuntimeState !== 'ready') {
1181
+ return 'loading';
1182
+ }
1183
+ return 'ready';
1184
+ }
1185
+ function buildTableConfig(config, rows) {
1186
+ const tableConfig = createDefaultTableConfig();
1187
+ const toolbar = tableConfig.toolbar;
1188
+ const behavior = tableConfig.behavior;
1189
+ const pagination = behavior.pagination;
1190
+ const sorting = behavior.sorting;
1191
+ const filtering = behavior.filtering;
1192
+ const selection = behavior.selection;
1193
+ const localDataMode = behavior.localDataMode;
1194
+ const actions = tableConfig.actions;
1195
+ const rowActions = actions.row;
1196
+ const bulkActions = actions.bulk;
1197
+ const appearance = tableConfig.appearance;
1198
+ const categoryField = config.axes?.x?.field || config.series[0]?.categoryField;
1199
+ const categoryLabel = config.axes?.x?.label || categoryField;
1200
+ const sampleRow = rows[0] || {};
1201
+ const seriesColumns = config.series
1202
+ .map((series) => {
1203
+ const field = series.metric?.field || series.categoryField;
1204
+ if (!field) {
1205
+ return null;
1206
+ }
1207
+ return {
1208
+ field,
1209
+ header: series.name || series.metric?.label || field,
1210
+ };
1211
+ })
1212
+ .filter((column) => column !== null);
1213
+ const fallbackColumns = Object.keys(sampleRow).map((field) => ({
1214
+ field,
1215
+ header: field,
1216
+ }));
1217
+ const categoryColumn = categoryField
1218
+ ? [{
1219
+ field: categoryField,
1220
+ header: categoryLabel || categoryField,
1221
+ }]
1222
+ : [];
1223
+ const dedupedColumns = [...categoryColumn, ...seriesColumns, ...fallbackColumns].filter((column, index, source) => source.findIndex((candidate) => candidate.field === column.field) === index);
1224
+ tableConfig.columns = dedupedColumns;
1225
+ toolbar.visible = false;
1226
+ pagination.enabled = rows.length > 10;
1227
+ sorting.enabled = true;
1228
+ filtering.enabled = false;
1229
+ selection.enabled = false;
1230
+ localDataMode.enabled = true;
1231
+ rowActions.enabled = false;
1232
+ rowActions.actions = [];
1233
+ bulkActions.enabled = false;
1234
+ bulkActions.actions = [];
1235
+ appearance.density = 'comfortable';
1236
+ return tableConfig;
1237
+ }
1238
+ function resolveText(value) {
1239
+ if (typeof value === 'string') {
1240
+ return value;
1241
+ }
1242
+ if (value && typeof value === 'object' && 'text' in value) {
1243
+ const text = value.text;
1244
+ return typeof text === 'string' ? text : '';
1245
+ }
1246
+ return '';
1247
+ }
1248
+ function stringifyCell(value) {
1249
+ if (value === null || value === undefined) {
1250
+ return undefined;
1251
+ }
1252
+ return String(value);
1253
+ }
1254
+ function buildRemoteSignature(config) {
1255
+ if (config.dataSource?.kind !== 'remote') {
1256
+ return null;
1257
+ }
1258
+ return JSON.stringify({
1259
+ id: config.id,
1260
+ resourcePath: config.dataSource.resourcePath,
1261
+ query: config.dataSource.query,
1262
+ });
1263
+ }
1264
+
1265
+ const PRAXIS_CHART_SHOWCASE_WIDGET_METADATA = {
1266
+ id: 'praxis-chart-showcase-widget',
1267
+ componentType: 'praxis-chart-showcase-widget',
1268
+ displayName: 'Praxis Chart Showcase Widget',
1269
+ selector: 'praxis-chart-showcase-widget',
1270
+ component: PraxisChartShowcaseWidgetComponent,
1271
+ friendlyName: 'Praxis Chart Showcase Widget',
1272
+ description: 'Showcase-only widget that toggles between Praxis chart and Praxis table using the same dataset.',
1273
+ icon: 'swap_horiz',
1274
+ tags: ['chart', 'table', 'showcase', 'analytics'],
1275
+ lib: '@praxisui/charts',
1276
+ inputs: [
1277
+ {
1278
+ name: 'config',
1279
+ type: 'PraxisChartConfig',
1280
+ description: 'Chart configuration preserved across chart and table views.',
1281
+ },
1282
+ {
1283
+ name: 'data',
1284
+ type: 'PraxisChartDataRow[]',
1285
+ description: 'Local dataset shared by the chart and table renderers.',
1286
+ },
1287
+ {
1288
+ name: 'viewMode',
1289
+ type: "'chart' | 'table'",
1290
+ description: 'Current showcase view mode.',
1291
+ },
1292
+ ],
1293
+ outputs: [
1294
+ {
1295
+ name: 'pointClick',
1296
+ type: 'PraxisChartPointEvent',
1297
+ description: 'Forwards point selection from the chart or mapped row selection from the table.',
1298
+ },
1299
+ {
1300
+ name: 'queryRequest',
1301
+ type: 'PraxisChartQueryRequestEvent',
1302
+ description: 'Forwards remote chart data requests.',
1303
+ },
1304
+ {
1305
+ name: 'loadStateChange',
1306
+ type: 'PraxisChartLoadState',
1307
+ description: 'Forwards chart runtime state transitions.',
1308
+ },
1309
+ ],
1310
+ };
1311
+ function providePraxisChartShowcaseWidgetMetadata() {
1312
+ return {
1313
+ provide: ENVIRONMENT_INITIALIZER,
1314
+ multi: true,
1315
+ useFactory: (registry) => () => {
1316
+ registry.register(PRAXIS_CHART_SHOWCASE_WIDGET_METADATA);
1317
+ },
1318
+ deps: [ComponentMetadataRegistry],
1319
+ };
1320
+ }
1321
+
1322
+ function providePraxisCharts() {
1323
+ return [
1324
+ EChartsEngineAdapter,
1325
+ {
1326
+ provide: PRAXIS_CHART_ENGINE,
1327
+ useExisting: EChartsEngineAdapter,
1328
+ },
1329
+ providePraxisChartsMetadata(),
1330
+ providePraxisChartDrilldownPanelMetadata(),
1331
+ providePraxisChartStateProbeMetadata(),
1332
+ providePraxisChartShowcaseWidgetMetadata(),
1333
+ ];
1334
+ }
1335
+
1336
+ class PraxisChartCanonicalContractMapperService {
1337
+ toPraxisChartConfig(contract) {
1338
+ this.assertSupportedContract(contract);
1339
+ return {
1340
+ id: contract.chartId,
1341
+ type: contract.kind,
1342
+ title: this.mapTextValue(contract.title),
1343
+ subtitle: this.mapTextValue(contract.subtitle),
1344
+ height: contract.height,
1345
+ axes: this.buildAxes(contract),
1346
+ series: this.buildSeries(contract),
1347
+ dataSource: this.buildDataSource(contract),
1348
+ interactions: this.buildInteractions(contract),
1349
+ theme: this.buildTheme(contract),
1350
+ emptyState: contract.state?.empty
1351
+ ? {
1352
+ title: this.mapTextValue(contract.state.empty.title),
1353
+ description: this.mapTextValue(contract.state.empty.description),
1354
+ }
1355
+ : undefined,
1356
+ state: {
1357
+ loadingLabel: this.mapTextValue(contract.state?.loading?.title),
1358
+ error: contract.state?.error
1359
+ ? {
1360
+ title: this.mapTextValue(contract.state.error.title),
1361
+ description: this.mapTextValue(contract.state.error.description),
1362
+ }
1363
+ : undefined,
1364
+ },
1365
+ };
1366
+ }
1367
+ assertSupportedContract(contract) {
1368
+ if (contract.source.kind === 'derived') {
1369
+ throw new Error('x-ui.chart source.kind="derived" is not yet implemented in @praxisui/charts.');
1370
+ }
1371
+ if (contract.source.kind !== 'praxis.stats') {
1372
+ throw new Error(`x-ui.chart source.kind="${contract.source.kind}" is not supported in @praxisui/charts.`);
1373
+ }
1374
+ if (!contract.source.resource?.trim()) {
1375
+ throw new Error('x-ui.chart source.resource is required for source.kind="praxis.stats".');
1376
+ }
1377
+ if (!contract.source.operation) {
1378
+ throw new Error('x-ui.chart source.operation is required for source.kind="praxis.stats".');
1379
+ }
1380
+ if (contract.theme?.palette && typeof contract.theme.palette === 'string') {
1381
+ throw new Error('x-ui.chart theme.palette as palette token reference is not yet implemented in @praxisui/charts.');
1382
+ }
1383
+ if (contract.theme?.variant) {
1384
+ throw new Error('x-ui.chart theme.variant is not yet implemented in @praxisui/charts.');
1385
+ }
1386
+ const aggregations = [
1387
+ ...(contract.metrics?.map((metric) => metric.aggregation).filter(Boolean) ?? []),
1388
+ ...(contract.aggregations?.map((aggregation) => aggregation.operation) ?? []),
1389
+ ];
1390
+ if (aggregations.includes('distinct-count')) {
1391
+ throw new Error('x-ui.chart aggregation "distinct-count" is not yet implemented in @praxisui/charts.');
1392
+ }
1393
+ if (!contract.metrics?.length) {
1394
+ throw new Error('x-ui.chart requires at least one metric for the current @praxisui/charts runtime.');
1395
+ }
1396
+ if (contract.kind !== 'pie' && contract.kind !== 'donut' && !contract.dimensions?.length) {
1397
+ throw new Error('x-ui.chart cartesian charts require at least one dimension in the current @praxisui/charts runtime.');
1398
+ }
1399
+ if ((contract.kind === 'pie' || contract.kind === 'donut') && !contract.dimensions?.[0]?.field) {
1400
+ throw new Error('x-ui.chart pie/donut charts require a first dimension for category mapping.');
1401
+ }
1402
+ if (contract.metrics.length > 1 && (contract.kind === 'pie' || contract.kind === 'donut')) {
1403
+ throw new Error('x-ui.chart pie/donut charts with multiple metrics are not yet implemented in @praxisui/charts.');
1404
+ }
1405
+ if (contract.source.kind === 'praxis.stats' && contract.metrics.length > 1) {
1406
+ throw new Error('x-ui.chart praxis.stats currently supports a single metric per chart in @praxisui/charts.');
1407
+ }
1408
+ if (contract.events?.selectionChange || contract.events?.crossFilter) {
1409
+ throw new Error('x-ui.chart selectionChange/crossFilter declarative runtime actions are not yet implemented in @praxisui/charts.');
1410
+ }
1411
+ }
1412
+ buildAxes(contract) {
1413
+ const firstDimension = contract.dimensions?.[0];
1414
+ const firstMetric = contract.metrics?.[0];
1415
+ if (contract.kind === 'pie' || contract.kind === 'donut') {
1416
+ return {
1417
+ x: {
1418
+ field: firstDimension?.field,
1419
+ label: this.toLabel(firstDimension?.label),
1420
+ },
1421
+ };
1422
+ }
1423
+ return {
1424
+ x: {
1425
+ field: firstDimension?.field,
1426
+ label: this.toLabel(firstDimension?.label),
1427
+ type: firstDimension?.role === 'time' ? 'time' : 'category',
1428
+ },
1429
+ y: {
1430
+ label: this.toLabel(firstMetric?.label),
1431
+ type: 'value',
1432
+ },
1433
+ };
1434
+ }
1435
+ buildSeries(contract) {
1436
+ const firstDimension = contract.dimensions?.[0];
1437
+ const labelsVisible = this.resolveToggle(contract.labels);
1438
+ return (contract.metrics ?? []).map((metric, index) => ({
1439
+ id: `${metric.field}-${index + 1}`,
1440
+ name: this.toLabel(metric.label) ?? metric.field,
1441
+ type: contract.kind === 'stacked-bar' ? 'bar' : contract.kind,
1442
+ categoryField: contract.kind === 'pie' || contract.kind === 'donut' ? firstDimension?.field : undefined,
1443
+ metric: {
1444
+ field: metric.field,
1445
+ aggregation: this.mapAggregation(metric.aggregation),
1446
+ label: this.toLabel(metric.label),
1447
+ },
1448
+ stackId: contract.kind === 'stacked-bar' ? 'stack-1' : undefined,
1449
+ labels: labelsVisible ? { visible: true } : undefined,
1450
+ smooth: contract.kind === 'line' || contract.kind === 'area' ? true : undefined,
1451
+ }));
1452
+ }
1453
+ buildDataSource(contract) {
1454
+ return {
1455
+ kind: 'remote',
1456
+ resourcePath: contract.source.resource,
1457
+ schemaId: contract.source.resource,
1458
+ query: this.buildQuery(contract),
1459
+ };
1460
+ }
1461
+ buildQuery(contract) {
1462
+ const filtersFromContract = this.toQueryFilterMap(contract.filters);
1463
+ const combinedFilters = {
1464
+ ...filtersFromContract,
1465
+ };
1466
+ return {
1467
+ sourceKind: 'praxis.stats',
1468
+ statsOperation: contract.source.operation,
1469
+ granularity: contract.source.options?.granularity,
1470
+ fillGaps: contract.source.options?.fillGaps,
1471
+ distributionMode: contract.source.options?.mode,
1472
+ bucketSize: contract.source.options?.bucketSize,
1473
+ bucketCount: contract.source.options?.bucketCount,
1474
+ statsOrderBy: this.mapStatsOrderBy(contract.source.options?.orderBy),
1475
+ statsPath: this.buildStatsPath(contract),
1476
+ statsRequest: this.buildStatsRequest(contract, combinedFilters),
1477
+ dimensions: contract.dimensions?.map((dimension) => dimension.field),
1478
+ metrics: contract.metrics?.map((metric) => ({
1479
+ field: metric.field,
1480
+ aggregation: this.mapAggregation(metric.aggregation),
1481
+ alias: metric.field,
1482
+ })),
1483
+ filters: Object.keys(combinedFilters).length ? combinedFilters : undefined,
1484
+ sort: contract.sort?.map((item) => `${item.field}:${item.direction}`),
1485
+ limit: contract.limit ?? contract.source.options?.limit,
1486
+ };
1487
+ }
1488
+ buildInteractions(contract) {
1489
+ return {
1490
+ pointClick: Boolean(contract.events?.pointClick || contract.events?.drillDown),
1491
+ selection: Boolean(contract.events?.selectionChange),
1492
+ drillDown: Boolean(contract.events?.drillDown),
1493
+ };
1494
+ }
1495
+ buildTheme(contract) {
1496
+ return {
1497
+ palette: Array.isArray(contract.theme?.palette) ? contract.theme.palette : undefined,
1498
+ legend: { visible: this.resolveToggle(contract.legend, true) },
1499
+ tooltip: {
1500
+ enabled: this.resolveToggle(contract.tooltip, true),
1501
+ trigger: contract.kind === 'pie' || contract.kind === 'donut' ? 'item' : 'axis',
1502
+ },
1503
+ };
1504
+ }
1505
+ mapTextValue(value) {
1506
+ if (!value)
1507
+ return undefined;
1508
+ if (typeof value === 'string')
1509
+ return value;
1510
+ return {
1511
+ key: value.key,
1512
+ text: value.fallback,
1513
+ };
1514
+ }
1515
+ toLabel(value) {
1516
+ if (!value)
1517
+ return undefined;
1518
+ if (typeof value === 'string')
1519
+ return value;
1520
+ return value.fallback || value.key;
1521
+ }
1522
+ resolveToggle(value, defaultValue = false) {
1523
+ if (typeof value === 'boolean')
1524
+ return value;
1525
+ if (typeof value === 'object' && value)
1526
+ return value.enabled;
1527
+ return defaultValue;
1528
+ }
1529
+ mapAggregation(aggregation) {
1530
+ switch (aggregation) {
1531
+ case 'avg':
1532
+ case 'min':
1533
+ case 'max':
1534
+ case 'count':
1535
+ case 'sum':
1536
+ return aggregation;
1537
+ case undefined:
1538
+ return undefined;
1539
+ default:
1540
+ throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
1541
+ }
1542
+ }
1543
+ toQueryFilterMap(filters) {
1544
+ if (!filters?.length)
1545
+ return undefined;
1546
+ return filters.reduce((acc, filter) => {
1547
+ if (filter.value !== undefined) {
1548
+ acc[filter.field] = filter.value;
1549
+ return acc;
1550
+ }
1551
+ if (filter.values !== undefined) {
1552
+ acc[filter.field] = filter.values;
1553
+ }
1554
+ return acc;
1555
+ }, {});
1556
+ }
1557
+ buildStatsPath(contract) {
1558
+ return `${contract.source.resource.replace(/\/+$/, '')}/stats/${contract.source.operation}`;
1559
+ }
1560
+ buildStatsRequest(contract, filters) {
1561
+ const field = this.resolveStatsField(contract);
1562
+ const metric = this.buildStatsMetricRequest(contract);
1563
+ const limit = contract.limit ?? contract.source.options?.limit;
1564
+ const orderBy = this.mapStatsOrderByToBackend(contract.source.options?.orderBy);
1565
+ switch (contract.source.operation) {
1566
+ case 'group-by':
1567
+ return {
1568
+ filter: filters,
1569
+ field,
1570
+ metric,
1571
+ limit,
1572
+ orderBy,
1573
+ };
1574
+ case 'timeseries':
1575
+ return {
1576
+ filter: filters,
1577
+ field,
1578
+ granularity: this.mapStatsGranularityToBackend(contract.source.options?.granularity),
1579
+ metric,
1580
+ fillGaps: contract.source.options?.fillGaps,
1581
+ };
1582
+ case 'distribution':
1583
+ return {
1584
+ filter: filters,
1585
+ field,
1586
+ mode: this.mapDistributionModeToBackend(contract.source.options?.mode),
1587
+ metric,
1588
+ bucketSize: contract.source.options?.bucketSize,
1589
+ bucketCount: contract.source.options?.bucketCount,
1590
+ limit,
1591
+ orderBy,
1592
+ };
1593
+ default:
1594
+ throw new Error(`x-ui.chart source.operation "${contract.source.operation}" is not supported in @praxisui/charts.`);
1595
+ }
1596
+ }
1597
+ resolveStatsField(contract) {
1598
+ const dimensionField = contract.dimensions?.[0]?.field?.trim();
1599
+ if (dimensionField) {
1600
+ return dimensionField;
1601
+ }
1602
+ throw new Error('x-ui.chart requires dimensions[0].field to derive the canonical praxis.stats request.');
1603
+ }
1604
+ buildStatsMetricRequest(contract) {
1605
+ const metric = contract.metrics?.[0];
1606
+ if (!metric) {
1607
+ throw new Error('x-ui.chart requires metrics[0] to derive the canonical praxis.stats request.');
1608
+ }
1609
+ const operation = this.mapStatsMetricOperation(metric.aggregation);
1610
+ return {
1611
+ operation,
1612
+ field: operation === 'COUNT' ? undefined : metric.field,
1613
+ };
1614
+ }
1615
+ mapStatsOrderBy(value) {
1616
+ if (!value)
1617
+ return undefined;
1618
+ switch (value) {
1619
+ case 'key-asc':
1620
+ case 'key-desc':
1621
+ case 'value-asc':
1622
+ case 'value-desc':
1623
+ return value;
1624
+ default:
1625
+ throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
1626
+ }
1627
+ }
1628
+ mapStatsOrderByToBackend(value) {
1629
+ switch (value) {
1630
+ case 'key-asc':
1631
+ return 'KEY_ASC';
1632
+ case 'key-desc':
1633
+ return 'KEY_DESC';
1634
+ case 'value-asc':
1635
+ return 'VALUE_ASC';
1636
+ case 'value-desc':
1637
+ return 'VALUE_DESC';
1638
+ case undefined:
1639
+ return undefined;
1640
+ default:
1641
+ throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
1642
+ }
1643
+ }
1644
+ mapStatsGranularityToBackend(value) {
1645
+ switch (value) {
1646
+ case 'hour':
1647
+ return 'HOUR';
1648
+ case 'day':
1649
+ return 'DAY';
1650
+ case 'week':
1651
+ return 'WEEK';
1652
+ case 'month':
1653
+ case undefined:
1654
+ return 'MONTH';
1655
+ case 'quarter':
1656
+ return 'QUARTER';
1657
+ case 'year':
1658
+ return 'YEAR';
1659
+ default:
1660
+ throw new Error(`x-ui.chart source.options.granularity "${value}" is not supported in @praxisui/charts.`);
1661
+ }
1662
+ }
1663
+ mapDistributionModeToBackend(value) {
1664
+ switch (value) {
1665
+ case 'histogram':
1666
+ return 'HISTOGRAM';
1667
+ case 'terms':
1668
+ case undefined:
1669
+ return 'TERMS';
1670
+ default:
1671
+ throw new Error(`x-ui.chart source.options.mode "${value}" is not supported in @praxisui/charts.`);
1672
+ }
1673
+ }
1674
+ mapStatsMetricOperation(aggregation) {
1675
+ switch (aggregation) {
1676
+ case undefined:
1677
+ case 'count':
1678
+ return 'COUNT';
1679
+ case 'sum':
1680
+ return 'SUM';
1681
+ case 'avg':
1682
+ return 'AVG';
1683
+ case 'min':
1684
+ return 'MIN';
1685
+ case 'max':
1686
+ return 'MAX';
1687
+ default:
1688
+ throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
1689
+ }
1690
+ }
1691
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1692
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, providedIn: 'root' });
1693
+ }
1694
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, decorators: [{
1695
+ type: Injectable,
1696
+ args: [{ providedIn: 'root' }]
1697
+ }] });
1698
+
1699
+ class PraxisChartMetadataRegistrationService {
1700
+ registry;
1701
+ registered = false;
1702
+ constructor(registry) {
1703
+ this.registry = registry;
1704
+ this.ensureRegistered();
1705
+ }
1706
+ ensureRegistered() {
1707
+ if (this.registered) {
1708
+ return;
1709
+ }
1710
+ this.registry.register(PRAXIS_CHART_COMPONENT_METADATA);
1711
+ this.registry.register(PRAXIS_CHART_DRILLDOWN_PANEL_METADATA);
1712
+ this.registry.register(PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA);
1713
+ this.registry.register(PRAXIS_CHART_SHOWCASE_WIDGET_METADATA);
1714
+ this.registered = true;
1715
+ }
1716
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, deps: [{ token: i1$1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Injectable });
1717
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, providedIn: 'root' });
1718
+ }
1719
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, decorators: [{
1720
+ type: Injectable,
1721
+ args: [{ providedIn: 'root' }]
1722
+ }], ctorParameters: () => [{ type: i1$1.ComponentMetadataRegistry }] });
1723
+
1724
+ class PraxisChartSchemaMapperService {
1725
+ metadataRegistration;
1726
+ canonicalContractMapper;
1727
+ constructor(metadataRegistration, canonicalContractMapper) {
1728
+ this.metadataRegistration = metadataRegistration;
1729
+ this.canonicalContractMapper = canonicalContractMapper;
1730
+ this.metadataRegistration.ensureRegistered();
1731
+ }
1732
+ resolve(input, defaults) {
1733
+ const schema = this.normalize(input);
1734
+ const config = this.toConfig(schema);
1735
+ const key = schema.key?.trim()
1736
+ || defaults?.key?.trim()
1737
+ || schema.chart?.chartId?.trim()
1738
+ || config.id?.trim()
1739
+ || 'praxis-chart';
1740
+ return {
1741
+ componentId: 'praxis-chart',
1742
+ key,
1743
+ className: schema.className,
1744
+ shell: schema.shell,
1745
+ layout: schema.layout ?? defaults?.layout,
1746
+ definition: this.toWidgetDefinition(schema),
1747
+ };
1748
+ }
1749
+ toWidgetDefinition(input) {
1750
+ const schema = this.normalize(input);
1751
+ return {
1752
+ id: 'praxis-chart',
1753
+ inputs: {
1754
+ config: this.toConfig(schema),
1755
+ data: schema.data ?? null,
1756
+ },
1757
+ outputs: this.toOutputs(schema),
1758
+ bindingOrder: schema.bindingOrder ?? ['config', 'data'],
1759
+ };
1760
+ }
1761
+ toWidgetInstance(input, defaults) {
1762
+ const resolved = this.resolve(input, defaults);
1763
+ return {
1764
+ key: resolved.key,
1765
+ definition: resolved.definition,
1766
+ className: resolved.className,
1767
+ shell: resolved.shell,
1768
+ };
1769
+ }
1770
+ toGridWidgetInstance(input, defaults) {
1771
+ const resolved = this.resolve(input, defaults);
1772
+ return {
1773
+ key: resolved.key,
1774
+ definition: resolved.definition,
1775
+ layout: resolved.layout ?? defaults.layout,
1776
+ className: resolved.className,
1777
+ shell: resolved.shell,
1778
+ };
1779
+ }
1780
+ normalize(input) {
1781
+ if (this.isCanonicalChartContract(input)) {
1782
+ return {
1783
+ kind: 'x-ui.chart',
1784
+ componentId: 'praxis-chart',
1785
+ chart: input,
1786
+ };
1787
+ }
1788
+ if (this.isWidgetSchema(input)) {
1789
+ return {
1790
+ kind: input.kind ?? 'x-ui.chart',
1791
+ componentId: 'praxis-chart',
1792
+ ...input,
1793
+ };
1794
+ }
1795
+ throw new Error('PraxisChartSchemaMapperService requires x-ui.chart as the primary public input.');
1796
+ }
1797
+ isWidgetSchema(input) {
1798
+ return typeof input === 'object'
1799
+ && input !== null
1800
+ && ('chart' in input
1801
+ || 'componentId' in input
1802
+ || 'key' in input
1803
+ || 'className' in input
1804
+ || 'outputs' in input
1805
+ || 'bindingOrder' in input
1806
+ || 'shell' in input
1807
+ || 'layout' in input
1808
+ || 'data' in input);
1809
+ }
1810
+ isCanonicalChartContract(input) {
1811
+ return typeof input === 'object'
1812
+ && input !== null
1813
+ && 'version' in input
1814
+ && 'kind' in input
1815
+ && 'source' in input;
1816
+ }
1817
+ toConfig(schema) {
1818
+ if (!schema.chart) {
1819
+ throw new Error('PraxisChartWidgetSchema requires widget.chart as the canonical public input.');
1820
+ }
1821
+ return this.canonicalContractMapper.toPraxisChartConfig(schema.chart);
1822
+ }
1823
+ toOutputs(schema) {
1824
+ if (schema.outputs) {
1825
+ return schema.outputs;
1826
+ }
1827
+ if (schema.chart?.events?.pointClick || schema.chart?.events?.drillDown) {
1828
+ return {
1829
+ pointClick: 'emit',
1830
+ };
1831
+ }
1832
+ return undefined;
1833
+ }
1834
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartSchemaMapperService, deps: [{ token: PraxisChartMetadataRegistrationService }, { token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
1835
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartSchemaMapperService, providedIn: 'root' });
1836
+ }
1837
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartSchemaMapperService, decorators: [{
1838
+ type: Injectable,
1839
+ args: [{ providedIn: 'root' }]
1840
+ }], ctorParameters: () => [{ type: PraxisChartMetadataRegistrationService }, { type: PraxisChartCanonicalContractMapperService }] });
1841
+
1842
+ class PraxisChartBackendPayloadAdapterService {
1843
+ chartSchemaMapper;
1844
+ canonicalContractMapper;
1845
+ constructor(chartSchemaMapper, canonicalContractMapper) {
1846
+ this.chartSchemaMapper = chartSchemaMapper;
1847
+ this.canonicalContractMapper = canonicalContractMapper;
1848
+ }
1849
+ toChartConfig(payload) {
1850
+ if (!payload.widget.chart) {
1851
+ throw new Error('PraxisChartBackendPayload requires widget.chart as the canonical public input.');
1852
+ }
1853
+ const baseConfig = this.canonicalContractMapper.toPraxisChartConfig(payload.widget.chart);
1854
+ return {
1855
+ ...baseConfig,
1856
+ preferredLoadState: payload.widget.runtime?.preferredLoadState ?? baseConfig.preferredLoadState,
1857
+ };
1858
+ }
1859
+ toWidgetInstance(payload) {
1860
+ const widget = this.chartSchemaMapper.toWidgetInstance({
1861
+ kind: payload.widget.kind ?? 'x-ui.chart',
1862
+ key: payload.widget.key,
1863
+ className: payload.widget.className,
1864
+ shell: payload.widget.shell,
1865
+ bindingOrder: payload.widget.bindingOrder,
1866
+ outputs: payload.widget.outputs,
1867
+ chart: payload.widget.chart,
1868
+ data: payload.data,
1869
+ });
1870
+ return {
1871
+ ...widget,
1872
+ definition: {
1873
+ ...widget.definition,
1874
+ inputs: {
1875
+ ...(widget.definition.inputs || {}),
1876
+ config: this.toChartConfig(payload),
1877
+ },
1878
+ },
1879
+ };
1880
+ }
1881
+ toGridWidgetInstance(payload) {
1882
+ const layout = payload.widget.layout ?? { col: 1, row: 1, colSpan: 6, rowSpan: 4 };
1883
+ const widget = this.chartSchemaMapper.toGridWidgetInstance({
1884
+ kind: payload.widget.kind ?? 'x-ui.chart',
1885
+ key: payload.widget.key,
1886
+ className: payload.widget.className,
1887
+ shell: payload.widget.shell,
1888
+ bindingOrder: payload.widget.bindingOrder,
1889
+ outputs: payload.widget.outputs,
1890
+ layout,
1891
+ chart: payload.widget.chart,
1892
+ data: payload.data,
1893
+ }, {
1894
+ key: payload.widget.key,
1895
+ layout,
1896
+ });
1897
+ return {
1898
+ ...widget,
1899
+ definition: {
1900
+ ...widget.definition,
1901
+ inputs: {
1902
+ ...(widget.definition.inputs || {}),
1903
+ config: this.toChartConfig(payload),
1904
+ },
1905
+ },
1906
+ };
1907
+ }
1908
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, deps: [{ token: PraxisChartSchemaMapperService }, { token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
1909
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, providedIn: 'root' });
1910
+ }
1911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, decorators: [{
1912
+ type: Injectable,
1913
+ args: [{ providedIn: 'root' }]
1914
+ }], ctorParameters: () => [{ type: PraxisChartSchemaMapperService }, { type: PraxisChartCanonicalContractMapperService }] });
1915
+
1916
+ const PRAXIS_CHART_BACKEND_MOCK_BAR = {
1917
+ schemaMeta: {
1918
+ schemaId: 'api/human-resources/vw-perfil-heroi|post|response|tenant:demo|locale:pt-BR',
1919
+ schemaHash: 'published-hash-hero-universe-v1',
1920
+ resourcePath: 'api/human-resources/vw-perfil-heroi',
1921
+ operation: 'post',
1922
+ schemaType: 'response',
1923
+ source: 'backend',
1924
+ },
1925
+ widget: {
1926
+ kind: 'x-ui.chart',
1927
+ key: 'monthly-revenue-chart',
1928
+ className: 'analytics-widget analytics-widget-revenue',
1929
+ layout: { col: 1, row: 1, colSpan: 6, rowSpan: 4 },
1930
+ shell: {
1931
+ title: 'Heroes por universo',
1932
+ subtitle: 'Group-by real consumindo praxis.stats publicado',
1933
+ },
1934
+ outputs: {
1935
+ pointClick: 'emit',
1936
+ loadStateChange: 'emit',
1937
+ },
1938
+ chart: {
1939
+ version: '0.1.0',
1940
+ kind: 'bar',
1941
+ preset: 'ranking',
1942
+ chartId: 'monthly-revenue-chart',
1943
+ title: { key: 'charts.heroes.universe.title', fallback: 'Heroes por universo' },
1944
+ subtitle: { key: 'charts.heroes.universe.subtitle', fallback: 'Group-by em /vw-perfil-heroi' },
1945
+ height: 320,
1946
+ source: {
1947
+ kind: 'praxis.stats',
1948
+ resource: 'api/human-resources/vw-perfil-heroi',
1949
+ operation: 'group-by',
1950
+ options: {
1951
+ orderBy: 'value-desc',
1952
+ limit: 10,
1953
+ },
1954
+ },
1955
+ dimensions: [
1956
+ {
1957
+ field: 'universo',
1958
+ label: 'Universo',
1959
+ role: 'category',
1960
+ },
1961
+ ],
1962
+ metrics: [
1963
+ {
1964
+ field: 'count',
1965
+ label: 'Total de heroes',
1966
+ aggregation: 'count',
1967
+ },
1968
+ ],
1969
+ groupBy: ['universo'],
1970
+ sort: [{ field: 'universo', direction: 'asc' }],
1971
+ legend: { enabled: true },
1972
+ tooltip: { enabled: true },
1973
+ labels: false,
1974
+ theme: {
1975
+ palette: ['#1263b4'],
1976
+ },
1977
+ state: {
1978
+ empty: {
1979
+ title: { key: 'charts.empty.title', fallback: 'Sem dados analiticos' },
1980
+ description: { key: 'charts.empty.description', fallback: 'O backend retornou zero linhas para este recorte.' },
1981
+ },
1982
+ loading: {
1983
+ title: { key: 'charts.loading.title', fallback: 'Carregando dados publicados...' },
1984
+ },
1985
+ error: {
1986
+ title: { key: 'charts.error.title', fallback: 'Falha no contrato analitico' },
1987
+ description: { key: 'charts.error.description', fallback: 'Use este cenario para validar falha controlada do runtime.' },
1988
+ },
1989
+ },
1990
+ events: {
1991
+ pointClick: {
1992
+ action: 'emit',
1993
+ },
1994
+ },
1995
+ },
1996
+ },
1997
+ };
1998
+ const PRAXIS_CHART_BACKEND_MOCK_TIMESERIES = {
1999
+ schemaMeta: {
2000
+ schemaId: 'api/human-resources/vw-indicadores-incidentes|post|response|tenant:demo|locale:pt-BR',
2001
+ schemaHash: 'published-hash-incident-timeseries-v1',
2002
+ resourcePath: 'api/human-resources/vw-indicadores-incidentes',
2003
+ operation: 'post',
2004
+ schemaType: 'response',
2005
+ source: 'backend',
2006
+ },
2007
+ widget: {
2008
+ kind: 'x-ui.chart',
2009
+ key: 'incident-timeseries-chart',
2010
+ layout: { col: 7, row: 1, colSpan: 5, rowSpan: 4 },
2011
+ shell: {
2012
+ title: 'Incidentes por mes',
2013
+ subtitle: 'Time series real consumindo praxis.stats publicado',
2014
+ },
2015
+ chart: {
2016
+ version: '0.1.0',
2017
+ kind: 'line',
2018
+ preset: 'kpi-trend',
2019
+ chartId: 'incident-timeseries-chart',
2020
+ title: { key: 'charts.incidents.timeline.title', fallback: 'Incidentes por mes' },
2021
+ subtitle: { key: 'charts.incidents.timeline.subtitle', fallback: 'Timeseries em /vw-indicadores-incidentes' },
2022
+ source: {
2023
+ kind: 'praxis.stats',
2024
+ resource: 'api/human-resources/vw-indicadores-incidentes',
2025
+ operation: 'timeseries',
2026
+ options: {
2027
+ granularity: 'month',
2028
+ fillGaps: false,
2029
+ },
2030
+ },
2031
+ dimensions: [
2032
+ {
2033
+ field: 'ocorridoEm',
2034
+ label: 'Ocorrido em',
2035
+ role: 'time',
2036
+ },
2037
+ ],
2038
+ metrics: [
2039
+ {
2040
+ field: 'count',
2041
+ label: 'Total de incidentes',
2042
+ aggregation: 'count',
2043
+ },
2044
+ ],
2045
+ legend: { enabled: true },
2046
+ tooltip: { enabled: true },
2047
+ labels: false,
2048
+ theme: {
2049
+ palette: ['#2b8a3e'],
2050
+ },
2051
+ },
2052
+ },
2053
+ };
2054
+ const PRAXIS_CHART_BACKEND_MOCK_DONUT = {
2055
+ schemaMeta: {
2056
+ schemaId: 'api/human-resources/vw-indicadores-incidentes|post|response|tenant:demo|locale:pt-BR',
2057
+ schemaHash: 'published-hash-incident-severity-v1',
2058
+ resourcePath: 'api/human-resources/vw-indicadores-incidentes',
2059
+ operation: 'post',
2060
+ schemaType: 'response',
2061
+ source: 'backend',
2062
+ },
2063
+ widget: {
2064
+ kind: 'x-ui.chart',
2065
+ key: 'ticket-status-chart',
2066
+ layout: { col: 7, row: 1, colSpan: 5, rowSpan: 4 },
2067
+ shell: {
2068
+ title: 'Severidade de incidentes',
2069
+ subtitle: 'Distribution real consumindo praxis.stats publicado',
2070
+ },
2071
+ chart: {
2072
+ version: '0.1.0',
2073
+ kind: 'donut',
2074
+ preset: 'distribution',
2075
+ chartId: 'ticket-status-chart',
2076
+ title: { key: 'charts.incidents.severity.title', fallback: 'Severidade de incidentes' },
2077
+ source: {
2078
+ kind: 'praxis.stats',
2079
+ resource: 'api/human-resources/vw-indicadores-incidentes',
2080
+ operation: 'distribution',
2081
+ options: {
2082
+ mode: 'terms',
2083
+ orderBy: 'value-desc',
2084
+ limit: 10,
2085
+ },
2086
+ },
2087
+ dimensions: [
2088
+ {
2089
+ field: 'severidade',
2090
+ label: 'Severidade',
2091
+ role: 'segment',
2092
+ },
2093
+ ],
2094
+ metrics: [
2095
+ {
2096
+ field: 'count',
2097
+ label: 'Total',
2098
+ aggregation: 'count',
2099
+ },
2100
+ ],
2101
+ labels: { enabled: true },
2102
+ legend: { enabled: true },
2103
+ tooltip: { enabled: true },
2104
+ theme: {
2105
+ palette: ['#1263b4', '#2b8a3e', '#f08c00', '#c92a2a'],
2106
+ },
2107
+ },
2108
+ },
2109
+ };
2110
+
2111
+ function buildPraxisChartMockWidgetPage(adapter) {
2112
+ return {
2113
+ widgets: [
2114
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_BAR)),
2115
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES)),
2116
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT)),
2117
+ ],
2118
+ layout: {
2119
+ orientation: 'columns',
2120
+ columns: 2,
2121
+ gap: '16px',
2122
+ },
2123
+ };
2124
+ }
2125
+ function buildPraxisChartMockGridPage(adapter) {
2126
+ return {
2127
+ widgets: [
2128
+ withShowcaseViewToggle(adapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_BAR)),
2129
+ withShowcaseViewToggle(adapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES)),
2130
+ withShowcaseViewToggle(adapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT)),
2131
+ ],
2132
+ options: {
2133
+ cols: 12,
2134
+ rowHeight: 88,
2135
+ gap: 16,
2136
+ },
2137
+ };
2138
+ }
2139
+ function buildPraxisChartInteractiveWidgetPage(adapter) {
2140
+ return {
2141
+ widgets: [
2142
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_BAR)),
2143
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES)),
2144
+ {
2145
+ key: 'chart-drilldown-panel',
2146
+ definition: {
2147
+ id: 'praxis-chart-drilldown-panel',
2148
+ inputs: {
2149
+ title: 'Drill-down por segmento',
2150
+ selection: null,
2151
+ },
2152
+ },
2153
+ shell: {
2154
+ title: 'Local Drill-down',
2155
+ subtitle: 'Filtrado por pointClick do chart principal',
2156
+ },
2157
+ },
2158
+ {
2159
+ key: 'chart-event-probe',
2160
+ definition: {
2161
+ id: 'praxis-chart-state-probe',
2162
+ inputs: {
2163
+ title: 'Eventos do chart principal',
2164
+ value: 'Clique em uma barra para inspecionar o payload emitido.',
2165
+ },
2166
+ },
2167
+ shell: {
2168
+ title: 'Runtime Probe',
2169
+ subtitle: 'Conectado via WidgetDefinition.connections',
2170
+ },
2171
+ },
2172
+ withShowcaseViewToggle(adapter.toWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT)),
2173
+ ],
2174
+ connections: [
2175
+ {
2176
+ from: { widget: 'monthly-revenue-chart', output: 'pointClick' },
2177
+ to: { widget: 'chart-event-probe', input: 'value' },
2178
+ },
2179
+ {
2180
+ from: { widget: 'monthly-revenue-chart', output: 'pointClick' },
2181
+ to: { widget: 'chart-drilldown-panel', input: 'selection' },
2182
+ },
2183
+ ],
2184
+ layout: {
2185
+ orientation: 'columns',
2186
+ columns: 2,
2187
+ gap: '16px',
2188
+ },
2189
+ };
2190
+ }
2191
+ function buildPraxisChartInteractiveGridPage(adapter) {
2192
+ return {
2193
+ widgets: [
2194
+ withShowcaseViewToggle(adapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_BAR)),
2195
+ buildDrilldownGridWidget(),
2196
+ withShowcaseViewToggle(adapter.toGridWidgetInstance({
2197
+ ...PRAXIS_CHART_BACKEND_MOCK_TIMESERIES,
2198
+ widget: {
2199
+ ...PRAXIS_CHART_BACKEND_MOCK_TIMESERIES.widget,
2200
+ layout: { col: 1, row: 5, colSpan: 6, rowSpan: 4 },
2201
+ },
2202
+ })),
2203
+ buildProbeGridWidget(),
2204
+ withShowcaseViewToggle(adapter.toGridWidgetInstance({
2205
+ ...PRAXIS_CHART_BACKEND_MOCK_DONUT,
2206
+ widget: {
2207
+ ...PRAXIS_CHART_BACKEND_MOCK_DONUT.widget,
2208
+ layout: { col: 7, row: 5, colSpan: 6, rowSpan: 4 },
2209
+ },
2210
+ })),
2211
+ ],
2212
+ connections: [
2213
+ {
2214
+ from: { widget: 'monthly-revenue-chart', output: 'pointClick' },
2215
+ to: { widget: 'chart-event-probe', input: 'value' },
2216
+ },
2217
+ {
2218
+ from: { widget: 'monthly-revenue-chart', output: 'pointClick' },
2219
+ to: { widget: 'chart-drilldown-panel', input: 'selection' },
2220
+ },
2221
+ ],
2222
+ options: {
2223
+ cols: 12,
2224
+ rowHeight: 88,
2225
+ gap: 16,
2226
+ },
2227
+ };
2228
+ }
2229
+ function buildDrilldownGridWidget() {
2230
+ return {
2231
+ key: 'chart-drilldown-panel',
2232
+ definition: {
2233
+ id: 'praxis-chart-drilldown-panel',
2234
+ inputs: {
2235
+ title: 'Drill-down por segmento',
2236
+ selection: null,
2237
+ },
2238
+ },
2239
+ layout: { col: 7, row: 1, colSpan: 6, rowSpan: 4 },
2240
+ shell: {
2241
+ title: 'Local Drill-down',
2242
+ subtitle: 'Filtrado por pointClick do chart principal',
2243
+ },
2244
+ };
2245
+ }
2246
+ function buildProbeGridWidget() {
2247
+ return {
2248
+ key: 'chart-event-probe',
2249
+ definition: {
2250
+ id: 'praxis-chart-state-probe',
2251
+ inputs: {
2252
+ title: 'Eventos do chart principal',
2253
+ value: 'Clique em uma barra para inspecionar o payload emitido.',
2254
+ },
2255
+ },
2256
+ layout: { col: 7, row: 9, colSpan: 6, rowSpan: 3 },
2257
+ shell: {
2258
+ title: 'Runtime Probe',
2259
+ subtitle: 'Conectado via WidgetDefinition.connections',
2260
+ },
2261
+ };
2262
+ }
2263
+ function withShowcaseViewToggle(widget, initialViewMode = 'chart') {
2264
+ return {
2265
+ ...widget,
2266
+ definition: {
2267
+ ...widget.definition,
2268
+ id: 'praxis-chart-showcase-widget',
2269
+ inputs: {
2270
+ ...(widget.definition.inputs || {}),
2271
+ viewMode: initialViewMode,
2272
+ },
2273
+ bindingOrder: ['config', 'data', 'viewMode'],
2274
+ },
2275
+ shell: {
2276
+ ...(widget.shell || {}),
2277
+ actions: [...(widget.shell?.actions || []), ...buildShowcaseToggleActions()],
2278
+ },
2279
+ };
2280
+ }
2281
+ function buildShowcaseToggleActions() {
2282
+ return [
2283
+ {
2284
+ id: 'show-chart',
2285
+ icon: 'bar_chart',
2286
+ tooltip: 'Ver grafico',
2287
+ variant: 'icon',
2288
+ command: 'pdx:set-input',
2289
+ payload: {
2290
+ input: 'viewMode',
2291
+ value: 'chart',
2292
+ },
2293
+ },
2294
+ {
2295
+ id: 'show-table',
2296
+ icon: 'table_view',
2297
+ tooltip: 'Ver tabela',
2298
+ variant: 'icon',
2299
+ command: 'pdx:set-input',
2300
+ payload: {
2301
+ input: 'viewMode',
2302
+ value: 'table',
2303
+ },
2304
+ },
2305
+ ];
2306
+ }
2307
+
2308
+ class PraxisChartCompositionShowcaseComponent {
2309
+ layoutMode = signal('widget', ...(ngDevMode ? [{ debugName: "layoutMode" }] : []));
2310
+ payloadMode = signal('group-by', ...(ngDevMode ? [{ debugName: "payloadMode" }] : []));
2311
+ scenarioMode = signal('baseline', ...(ngDevMode ? [{ debugName: "scenarioMode" }] : []));
2312
+ runtimeContext = {
2313
+ tenantId: 'demo-enterprise',
2314
+ locale: 'pt-BR',
2315
+ environment: 'published-api',
2316
+ };
2317
+ backendPayloadAdapter = inject(PraxisChartBackendPayloadAdapterService);
2318
+ widgetPage = computed(() => {
2319
+ switch (this.scenarioMode()) {
2320
+ case 'interactive':
2321
+ return buildPraxisChartInteractiveWidgetPage(this.backendPayloadAdapter);
2322
+ case 'empty':
2323
+ return {
2324
+ ...buildPraxisChartMockWidgetPage(this.backendPayloadAdapter),
2325
+ widgets: [
2326
+ this.backendPayloadAdapter.toWidgetInstance({
2327
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2328
+ data: [],
2329
+ }),
2330
+ ],
2331
+ };
2332
+ case 'loading':
2333
+ return {
2334
+ ...buildPraxisChartMockWidgetPage(this.backendPayloadAdapter),
2335
+ widgets: [
2336
+ this.backendPayloadAdapter.toWidgetInstance({
2337
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2338
+ data: [],
2339
+ widget: {
2340
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR.widget,
2341
+ runtime: {
2342
+ preferredLoadState: 'loading',
2343
+ },
2344
+ },
2345
+ }),
2346
+ ],
2347
+ };
2348
+ case 'error':
2349
+ return {
2350
+ ...buildPraxisChartMockWidgetPage(this.backendPayloadAdapter),
2351
+ widgets: [
2352
+ this.backendPayloadAdapter.toWidgetInstance({
2353
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2354
+ data: [],
2355
+ widget: {
2356
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR.widget,
2357
+ runtime: {
2358
+ preferredLoadState: 'error',
2359
+ },
2360
+ },
2361
+ }),
2362
+ ],
2363
+ };
2364
+ case 'baseline':
2365
+ default:
2366
+ return buildPraxisChartMockWidgetPage(this.backendPayloadAdapter);
2367
+ }
2368
+ }, ...(ngDevMode ? [{ debugName: "widgetPage" }] : []));
2369
+ gridPage = computed(() => {
2370
+ switch (this.scenarioMode()) {
2371
+ case 'interactive':
2372
+ return buildPraxisChartInteractiveGridPage(this.backendPayloadAdapter);
2373
+ case 'empty':
2374
+ return {
2375
+ ...buildPraxisChartMockGridPage(this.backendPayloadAdapter),
2376
+ widgets: [
2377
+ this.backendPayloadAdapter.toGridWidgetInstance({
2378
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2379
+ data: [],
2380
+ }),
2381
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES),
2382
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT),
2383
+ ],
2384
+ };
2385
+ case 'loading':
2386
+ return {
2387
+ ...buildPraxisChartMockGridPage(this.backendPayloadAdapter),
2388
+ widgets: [
2389
+ this.backendPayloadAdapter.toGridWidgetInstance({
2390
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2391
+ data: [],
2392
+ widget: {
2393
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR.widget,
2394
+ runtime: {
2395
+ preferredLoadState: 'loading',
2396
+ },
2397
+ },
2398
+ }),
2399
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES),
2400
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT),
2401
+ ],
2402
+ };
2403
+ case 'error':
2404
+ return {
2405
+ ...buildPraxisChartMockGridPage(this.backendPayloadAdapter),
2406
+ widgets: [
2407
+ this.backendPayloadAdapter.toGridWidgetInstance({
2408
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR,
2409
+ data: [],
2410
+ widget: {
2411
+ ...PRAXIS_CHART_BACKEND_MOCK_BAR.widget,
2412
+ runtime: {
2413
+ preferredLoadState: 'error',
2414
+ },
2415
+ },
2416
+ }),
2417
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_TIMESERIES),
2418
+ this.backendPayloadAdapter.toGridWidgetInstance(PRAXIS_CHART_BACKEND_MOCK_DONUT),
2419
+ ],
2420
+ };
2421
+ case 'baseline':
2422
+ default:
2423
+ return buildPraxisChartMockGridPage(this.backendPayloadAdapter);
2424
+ }
2425
+ }, ...(ngDevMode ? [{ debugName: "gridPage" }] : []));
2426
+ payloadTitle = computed(() => {
2427
+ switch (this.payloadMode()) {
2428
+ case 'timeseries':
2429
+ return 'Envelope timeseries';
2430
+ case 'distribution':
2431
+ return 'Envelope distribution';
2432
+ case 'group-by':
2433
+ default:
2434
+ return 'Envelope group-by';
2435
+ }
2436
+ }, ...(ngDevMode ? [{ debugName: "payloadTitle" }] : []));
2437
+ selectedPayloadJson = computed(() => JSON.stringify(this.selectedPayload(), null, 2), ...(ngDevMode ? [{ debugName: "selectedPayloadJson" }] : []));
2438
+ selectedPayload = computed(() => {
2439
+ switch (this.payloadMode()) {
2440
+ case 'timeseries':
2441
+ return PRAXIS_CHART_BACKEND_MOCK_TIMESERIES;
2442
+ case 'distribution':
2443
+ return PRAXIS_CHART_BACKEND_MOCK_DONUT;
2444
+ case 'group-by':
2445
+ default:
2446
+ return PRAXIS_CHART_BACKEND_MOCK_BAR;
2447
+ }
2448
+ }, ...(ngDevMode ? [{ debugName: "selectedPayload" }] : []));
2449
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCompositionShowcaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2450
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartCompositionShowcaseComponent, isStandalone: true, selector: "praxis-chart-composition-showcase", providers: [providePraxisCharts()], ngImport: i0, template: `
2451
+ <section class="showcase-shell">
2452
+ <header class="showcase-hero">
2453
+ <div class="showcase-copy">
2454
+ <p class="showcase-eyebrow">Praxis Charts</p>
2455
+ <h2>Composicao local com contrato canonico e API publicada</h2>
2456
+ <p>
2457
+ O showcase valida o fluxo <code>widget.chart</code>,
2458
+ <code>praxis.stats</code>, renderizacao dinamica, probe de eventos
2459
+ e o toggle de chart/tabela sobre o mesmo dataset.
2460
+ </p>
2461
+ </div>
2462
+
2463
+ <div class="showcase-controls">
2464
+ <div class="control-group">
2465
+ <span>Layout</span>
2466
+ <div class="control-buttons">
2467
+ <button type="button" [class.active]="layoutMode() === 'widget'" (click)="layoutMode.set('widget')">
2468
+ Widget Page
2469
+ </button>
2470
+ <button type="button" [class.active]="layoutMode() === 'grid'" (click)="layoutMode.set('grid')">
2471
+ Grid Page
2472
+ </button>
2473
+ </div>
2474
+ </div>
2475
+
2476
+ <div class="control-group">
2477
+ <span>Envelope inspecionado</span>
2478
+ <div class="control-buttons">
2479
+ <button type="button" [class.active]="payloadMode() === 'group-by'" (click)="payloadMode.set('group-by')">
2480
+ Group-by
2481
+ </button>
2482
+ <button type="button" [class.active]="payloadMode() === 'timeseries'" (click)="payloadMode.set('timeseries')">
2483
+ Time series
2484
+ </button>
2485
+ <button type="button" [class.active]="payloadMode() === 'distribution'" (click)="payloadMode.set('distribution')">
2486
+ Distribution
2487
+ </button>
2488
+ </div>
2489
+ </div>
2490
+
2491
+ <div class="control-group">
2492
+ <span>Cenario</span>
2493
+ <div class="control-buttons">
2494
+ <button type="button" [class.active]="scenarioMode() === 'baseline'" (click)="scenarioMode.set('baseline')">
2495
+ Baseline
2496
+ </button>
2497
+ <button type="button" [class.active]="scenarioMode() === 'interactive'" (click)="scenarioMode.set('interactive')">
2498
+ Interativo
2499
+ </button>
2500
+ <button type="button" [class.active]="scenarioMode() === 'empty'" (click)="scenarioMode.set('empty')">
2501
+ Empty
2502
+ </button>
2503
+ <button type="button" [class.active]="scenarioMode() === 'loading'" (click)="scenarioMode.set('loading')">
2504
+ Loading
2505
+ </button>
2506
+ <button type="button" [class.active]="scenarioMode() === 'error'" (click)="scenarioMode.set('error')">
2507
+ Error
2508
+ </button>
2509
+ </div>
2510
+ </div>
2511
+ </div>
2512
+ </header>
2513
+
2514
+ <div class="showcase-grid">
2515
+ <article class="runtime-panel">
2516
+ <div class="panel-header">
2517
+ <div>
2518
+ <p class="panel-kicker">Runtime</p>
2519
+ <h3>{{ layoutMode() === 'widget' ? 'DynamicWidgetPage' : 'DynamicGridPage' }}</h3>
2520
+ </div>
2521
+ <span class="panel-chip">{{ layoutMode() }} · {{ scenarioMode() }}</span>
2522
+ </div>
2523
+
2524
+ @if (layoutMode() === 'widget') {
2525
+ <praxis-dynamic-page
2526
+ [page]="widgetPage()"
2527
+ [context]="runtimeContext"
2528
+ [autoPersist]="false"
2529
+ ></praxis-dynamic-page>
2530
+ } @else {
2531
+ <praxis-dynamic-grid-page [page]="gridPage()" [context]="runtimeContext"></praxis-dynamic-grid-page>
2532
+ }
2533
+ </article>
2534
+
2535
+ <article class="payload-panel">
2536
+ <div class="panel-header">
2537
+ <div>
2538
+ <p class="panel-kicker">Payload</p>
2539
+ <h3>{{ payloadTitle() }}</h3>
2540
+ </div>
2541
+ <span class="panel-chip">widget.chart</span>
2542
+ </div>
2543
+
2544
+ <pre>{{ selectedPayloadJson() }}</pre>
2545
+ </article>
2546
+ </div>
2547
+ </section>
2548
+ `, isInline: true, styles: [":host{display:block}.showcase-shell{display:grid;gap:24px}.showcase-hero{display:grid;gap:20px;padding:28px;border-radius:28px;background:radial-gradient(circle at top right,rgba(18,99,180,.18),transparent 30%),linear-gradient(135deg,#071836f2,#1263b4c7);color:#f7fbff}.showcase-copy h2{margin:0 0 10px;font-size:clamp(1.8rem,3vw,2.6rem);line-height:1.05}.showcase-copy p{margin:0;max-width:52rem;color:#f7fbffe0}.showcase-eyebrow,.panel-kicker{margin:0 0 8px;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:#f7fbffb8}.showcase-controls{display:flex;flex-wrap:wrap;gap:16px}.control-group{display:grid;gap:8px}.control-group span{font-size:.84rem;color:#f7fbffc2}.control-buttons{display:flex;flex-wrap:wrap;gap:8px}.control-buttons button{border:1px solid rgba(247,251,255,.22);background:#f7fbff14;color:#f7fbff;border-radius:999px;padding:10px 14px;cursor:pointer;transition:background .16s ease,transform .16s ease}.control-buttons button.active{background:#f7fbff;color:#0d2d5f}.showcase-grid{display:grid;gap:20px;grid-template-columns:minmax(0,1.3fr) minmax(320px,.9fr)}.runtime-panel,.payload-panel{display:grid;gap:16px;padding:20px;border-radius:24px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 76%,transparent);background:linear-gradient(180deg,#fffffff0,#f4f7fbf5)}.panel-header{display:flex;align-items:start;justify-content:space-between;gap:12px}.panel-header h3{margin:0;color:#142033}.panel-chip{border-radius:999px;padding:6px 10px;font-size:.76rem;background:#1263b41f;color:#1263b4}.payload-panel pre{margin:0;padding:16px;border-radius:18px;background:#09111f;color:#d7e6ff;overflow:auto;font-size:.84rem;line-height:1.5}@media(max-width:1080px){.showcase-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId"], outputs: ["pageChange"] }, { kind: "component", type: DynamicGridPageComponent, selector: "praxis-dynamic-grid-page", inputs: ["page", "context", "strictValidation", "gridOptions", "enableCustomization"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2549
+ }
2550
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCompositionShowcaseComponent, decorators: [{
2551
+ type: Component,
2552
+ args: [{ selector: 'praxis-chart-composition-showcase', standalone: true, imports: [CommonModule, DynamicWidgetPageComponent, DynamicGridPageComponent], providers: [providePraxisCharts()], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2553
+ <section class="showcase-shell">
2554
+ <header class="showcase-hero">
2555
+ <div class="showcase-copy">
2556
+ <p class="showcase-eyebrow">Praxis Charts</p>
2557
+ <h2>Composicao local com contrato canonico e API publicada</h2>
2558
+ <p>
2559
+ O showcase valida o fluxo <code>widget.chart</code>,
2560
+ <code>praxis.stats</code>, renderizacao dinamica, probe de eventos
2561
+ e o toggle de chart/tabela sobre o mesmo dataset.
2562
+ </p>
2563
+ </div>
2564
+
2565
+ <div class="showcase-controls">
2566
+ <div class="control-group">
2567
+ <span>Layout</span>
2568
+ <div class="control-buttons">
2569
+ <button type="button" [class.active]="layoutMode() === 'widget'" (click)="layoutMode.set('widget')">
2570
+ Widget Page
2571
+ </button>
2572
+ <button type="button" [class.active]="layoutMode() === 'grid'" (click)="layoutMode.set('grid')">
2573
+ Grid Page
2574
+ </button>
2575
+ </div>
2576
+ </div>
2577
+
2578
+ <div class="control-group">
2579
+ <span>Envelope inspecionado</span>
2580
+ <div class="control-buttons">
2581
+ <button type="button" [class.active]="payloadMode() === 'group-by'" (click)="payloadMode.set('group-by')">
2582
+ Group-by
2583
+ </button>
2584
+ <button type="button" [class.active]="payloadMode() === 'timeseries'" (click)="payloadMode.set('timeseries')">
2585
+ Time series
2586
+ </button>
2587
+ <button type="button" [class.active]="payloadMode() === 'distribution'" (click)="payloadMode.set('distribution')">
2588
+ Distribution
2589
+ </button>
2590
+ </div>
2591
+ </div>
2592
+
2593
+ <div class="control-group">
2594
+ <span>Cenario</span>
2595
+ <div class="control-buttons">
2596
+ <button type="button" [class.active]="scenarioMode() === 'baseline'" (click)="scenarioMode.set('baseline')">
2597
+ Baseline
2598
+ </button>
2599
+ <button type="button" [class.active]="scenarioMode() === 'interactive'" (click)="scenarioMode.set('interactive')">
2600
+ Interativo
2601
+ </button>
2602
+ <button type="button" [class.active]="scenarioMode() === 'empty'" (click)="scenarioMode.set('empty')">
2603
+ Empty
2604
+ </button>
2605
+ <button type="button" [class.active]="scenarioMode() === 'loading'" (click)="scenarioMode.set('loading')">
2606
+ Loading
2607
+ </button>
2608
+ <button type="button" [class.active]="scenarioMode() === 'error'" (click)="scenarioMode.set('error')">
2609
+ Error
2610
+ </button>
2611
+ </div>
2612
+ </div>
2613
+ </div>
2614
+ </header>
2615
+
2616
+ <div class="showcase-grid">
2617
+ <article class="runtime-panel">
2618
+ <div class="panel-header">
2619
+ <div>
2620
+ <p class="panel-kicker">Runtime</p>
2621
+ <h3>{{ layoutMode() === 'widget' ? 'DynamicWidgetPage' : 'DynamicGridPage' }}</h3>
2622
+ </div>
2623
+ <span class="panel-chip">{{ layoutMode() }} · {{ scenarioMode() }}</span>
2624
+ </div>
2625
+
2626
+ @if (layoutMode() === 'widget') {
2627
+ <praxis-dynamic-page
2628
+ [page]="widgetPage()"
2629
+ [context]="runtimeContext"
2630
+ [autoPersist]="false"
2631
+ ></praxis-dynamic-page>
2632
+ } @else {
2633
+ <praxis-dynamic-grid-page [page]="gridPage()" [context]="runtimeContext"></praxis-dynamic-grid-page>
2634
+ }
2635
+ </article>
2636
+
2637
+ <article class="payload-panel">
2638
+ <div class="panel-header">
2639
+ <div>
2640
+ <p class="panel-kicker">Payload</p>
2641
+ <h3>{{ payloadTitle() }}</h3>
2642
+ </div>
2643
+ <span class="panel-chip">widget.chart</span>
2644
+ </div>
2645
+
2646
+ <pre>{{ selectedPayloadJson() }}</pre>
2647
+ </article>
2648
+ </div>
2649
+ </section>
2650
+ `, styles: [":host{display:block}.showcase-shell{display:grid;gap:24px}.showcase-hero{display:grid;gap:20px;padding:28px;border-radius:28px;background:radial-gradient(circle at top right,rgba(18,99,180,.18),transparent 30%),linear-gradient(135deg,#071836f2,#1263b4c7);color:#f7fbff}.showcase-copy h2{margin:0 0 10px;font-size:clamp(1.8rem,3vw,2.6rem);line-height:1.05}.showcase-copy p{margin:0;max-width:52rem;color:#f7fbffe0}.showcase-eyebrow,.panel-kicker{margin:0 0 8px;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:#f7fbffb8}.showcase-controls{display:flex;flex-wrap:wrap;gap:16px}.control-group{display:grid;gap:8px}.control-group span{font-size:.84rem;color:#f7fbffc2}.control-buttons{display:flex;flex-wrap:wrap;gap:8px}.control-buttons button{border:1px solid rgba(247,251,255,.22);background:#f7fbff14;color:#f7fbff;border-radius:999px;padding:10px 14px;cursor:pointer;transition:background .16s ease,transform .16s ease}.control-buttons button.active{background:#f7fbff;color:#0d2d5f}.showcase-grid{display:grid;gap:20px;grid-template-columns:minmax(0,1.3fr) minmax(320px,.9fr)}.runtime-panel,.payload-panel{display:grid;gap:16px;padding:20px;border-radius:24px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 76%,transparent);background:linear-gradient(180deg,#fffffff0,#f4f7fbf5)}.panel-header{display:flex;align-items:start;justify-content:space-between;gap:12px}.panel-header h3{margin:0;color:#142033}.panel-chip{border-radius:999px;padding:6px 10px;font-size:.76rem;background:#1263b41f;color:#1263b4}.payload-panel pre{margin:0;padding:16px;border-radius:18px;background:#09111f;color:#d7e6ff;overflow:auto;font-size:.84rem;line-height:1.5}@media(max-width:1080px){.showcase-grid{grid-template-columns:1fr}}\n"] }]
2651
+ }] });
2652
+
2653
+ /*
2654
+ * Public API Surface of praxis-charts
2655
+ */
2656
+
2657
+ /**
2658
+ * Generated bundle index. Do not edit.
2659
+ */
2660
+
2661
+ export { PRAXIS_CHART_BACKEND_MOCK_BAR, PRAXIS_CHART_BACKEND_MOCK_DONUT, PRAXIS_CHART_BACKEND_MOCK_TIMESERIES, PRAXIS_CHART_COMPONENT_METADATA, PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH, PRAXIS_CHART_DRILLDOWN_PANEL_METADATA, PRAXIS_CHART_ENGINE, PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA, PraxisChartBackendPayloadAdapterService, PraxisChartCanonicalContractMapperService, PraxisChartComponent, PraxisChartCompositionShowcaseComponent, PraxisChartDataTransformerService, PraxisChartDrilldownPanelComponent, PraxisChartOptionBuilderService, PraxisChartSchemaMapperService, PraxisChartStateProbeComponent, PraxisChartStatsApiService, buildPraxisChartInteractiveGridPage, buildPraxisChartInteractiveWidgetPage, buildPraxisChartMockGridPage, buildPraxisChartMockWidgetPage, providePraxisChartDrilldownPanelMetadata, providePraxisChartStateProbeMetadata, providePraxisCharts, providePraxisChartsMetadata };
2662
+ //# sourceMappingURL=praxisui-charts.mjs.map