@praxisui/charts 8.0.0-beta.31 → 8.0.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/praxisui-charts.mjs +171 -173
- package/package.json +11 -10
- package/{index.d.ts → types/praxisui-charts.d.ts} +15 -15
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { CommonModule } from '@angular/common';
|
|
2
1
|
import * as i0 from '@angular/core';
|
|
3
2
|
import { Injectable, Inject, InjectionToken, input, booleanAttribute, output, viewChild, inject, ElementRef, DestroyRef, signal, computed, afterNextRender, effect, ChangeDetectionStrategy, Component, ViewChild, Input, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
3
|
import * as i1$1 from '@praxisui/core';
|
|
@@ -262,10 +261,10 @@ class PraxisChartDataTransformerService {
|
|
|
262
261
|
}
|
|
263
262
|
return null;
|
|
264
263
|
}
|
|
265
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
266
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartDataTransformerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
265
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartDataTransformerService, providedIn: 'root' });
|
|
267
266
|
}
|
|
268
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartDataTransformerService, decorators: [{
|
|
269
268
|
type: Injectable,
|
|
270
269
|
args: [{ providedIn: 'root' }]
|
|
271
270
|
}] });
|
|
@@ -684,10 +683,10 @@ class PraxisChartOptionBuilderService {
|
|
|
684
683
|
}
|
|
685
684
|
return Math.min(Math.max(value, 0), 20);
|
|
686
685
|
}
|
|
687
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
688
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
686
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartOptionBuilderService, deps: [{ token: PraxisChartDataTransformerService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
687
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartOptionBuilderService, providedIn: 'root' });
|
|
689
688
|
}
|
|
690
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
689
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartOptionBuilderService, decorators: [{
|
|
691
690
|
type: Injectable,
|
|
692
691
|
args: [{ providedIn: 'root' }]
|
|
693
692
|
}], ctorParameters: () => [{ type: PraxisChartDataTransformerService }] });
|
|
@@ -802,10 +801,10 @@ class EChartsEngineAdapter {
|
|
|
802
801
|
data: pointData(category, value),
|
|
803
802
|
});
|
|
804
803
|
}
|
|
805
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
806
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
804
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EChartsEngineAdapter, deps: [{ token: PraxisChartOptionBuilderService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
805
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EChartsEngineAdapter });
|
|
807
806
|
}
|
|
808
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
807
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EChartsEngineAdapter, decorators: [{
|
|
809
808
|
type: Injectable
|
|
810
809
|
}], ctorParameters: () => [{ type: PraxisChartOptionBuilderService }] });
|
|
811
810
|
function firstOptionEntry(value) {
|
|
@@ -1031,10 +1030,10 @@ class ChartContractNormalizerService {
|
|
|
1031
1030
|
omitUndefined(value) {
|
|
1032
1031
|
return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
|
|
1033
1032
|
}
|
|
1034
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1035
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1033
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1034
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractNormalizerService, providedIn: 'root' });
|
|
1036
1035
|
}
|
|
1037
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1036
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractNormalizerService, decorators: [{
|
|
1038
1037
|
type: Injectable,
|
|
1039
1038
|
args: [{ providedIn: 'root' }]
|
|
1040
1039
|
}] });
|
|
@@ -1210,10 +1209,10 @@ class ChartContractValidationService {
|
|
|
1210
1209
|
return (/^\d+(\.\d+)?$/.test(trimmed)
|
|
1211
1210
|
|| /^\d+(\.\d+)?\s*\/\s*\d+(\.\d+)?$/.test(trimmed));
|
|
1212
1211
|
}
|
|
1213
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1214
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1212
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1213
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractValidationService, providedIn: 'root' });
|
|
1215
1214
|
}
|
|
1216
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1215
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartContractValidationService, decorators: [{
|
|
1217
1216
|
type: Injectable,
|
|
1218
1217
|
args: [{ providedIn: 'root' }]
|
|
1219
1218
|
}] });
|
|
@@ -1644,10 +1643,10 @@ class PraxisChartCanonicalContractMapperService {
|
|
|
1644
1643
|
throw new Error(`x-ui.chart aggregation "${aggregation}" is not supported in @praxisui/charts.`);
|
|
1645
1644
|
}
|
|
1646
1645
|
}
|
|
1647
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1648
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1646
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartCanonicalContractMapperService, deps: [{ token: ChartContractNormalizerService }, { token: ChartContractValidationService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1647
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartCanonicalContractMapperService, providedIn: 'root' });
|
|
1649
1648
|
}
|
|
1650
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1649
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartCanonicalContractMapperService, decorators: [{
|
|
1651
1650
|
type: Injectable,
|
|
1652
1651
|
args: [{ providedIn: 'root' }]
|
|
1653
1652
|
}], ctorParameters: () => [{ type: ChartContractNormalizerService }, { type: ChartContractValidationService }] });
|
|
@@ -1865,10 +1864,10 @@ class PraxisChartStatsApiService {
|
|
|
1865
1864
|
}
|
|
1866
1865
|
return throwError(() => error instanceof Error ? error : new Error('Unexpected failure during praxis.stats execution.'));
|
|
1867
1866
|
}
|
|
1868
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1869
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1867
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartStatsApiService, deps: [{ token: i1.HttpClient }, { token: API_URL }, { token: i1$1.PraxisI18nService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1868
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartStatsApiService, providedIn: 'root' });
|
|
1870
1869
|
}
|
|
1871
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1870
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartStatsApiService, decorators: [{
|
|
1872
1871
|
type: Injectable,
|
|
1873
1872
|
args: [{ providedIn: 'root' }]
|
|
1874
1873
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
|
|
@@ -1879,16 +1878,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1879
1878
|
const PRAXIS_CHART_ENGINE = new InjectionToken('PRAXIS_CHART_ENGINE');
|
|
1880
1879
|
|
|
1881
1880
|
class PraxisChartComponent {
|
|
1882
|
-
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
1883
|
-
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1884
|
-
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : []));
|
|
1885
|
-
filterCriteria = input(null, ...(ngDevMode ? [{ debugName: "filterCriteria" }] : []));
|
|
1886
|
-
queryContext = input(null, ...(ngDevMode ? [{ debugName: "queryContext" }] : []));
|
|
1887
|
-
remoteDataResolver = input(null, ...(ngDevMode ? [{ debugName: "remoteDataResolver" }] : []));
|
|
1888
|
-
enableCustomization = input(false, ...(ngDevMode ?
|
|
1889
|
-
availableResources = input([], ...(ngDevMode ? [{ debugName: "availableResources" }] : []));
|
|
1890
|
-
availableFields = input([], ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
1891
|
-
availableTargets = input([], ...(ngDevMode ? [{ debugName: "availableTargets" }] : []));
|
|
1881
|
+
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
1882
|
+
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
1883
|
+
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : /* istanbul ignore next */ []));
|
|
1884
|
+
filterCriteria = input(null, ...(ngDevMode ? [{ debugName: "filterCriteria" }] : /* istanbul ignore next */ []));
|
|
1885
|
+
queryContext = input(null, ...(ngDevMode ? [{ debugName: "queryContext" }] : /* istanbul ignore next */ []));
|
|
1886
|
+
remoteDataResolver = input(null, ...(ngDevMode ? [{ debugName: "remoteDataResolver" }] : /* istanbul ignore next */ []));
|
|
1887
|
+
enableCustomization = input(false, { ...(ngDevMode ? { debugName: "enableCustomization" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1888
|
+
availableResources = input([], ...(ngDevMode ? [{ debugName: "availableResources" }] : /* istanbul ignore next */ []));
|
|
1889
|
+
availableFields = input([], ...(ngDevMode ? [{ debugName: "availableFields" }] : /* istanbul ignore next */ []));
|
|
1890
|
+
availableTargets = input([], ...(ngDevMode ? [{ debugName: "availableTargets" }] : /* istanbul ignore next */ []));
|
|
1892
1891
|
pointClick = output();
|
|
1893
1892
|
selectionChange = output();
|
|
1894
1893
|
crossFilter = output();
|
|
@@ -1896,7 +1895,7 @@ class PraxisChartComponent {
|
|
|
1896
1895
|
loadStateChange = output();
|
|
1897
1896
|
chartDocumentApplied = output();
|
|
1898
1897
|
chartDocumentSaved = output();
|
|
1899
|
-
chartHost = viewChild('chartHost', ...(ngDevMode ? [{ debugName: "chartHost" }] : []));
|
|
1898
|
+
chartHost = viewChild('chartHost', ...(ngDevMode ? [{ debugName: "chartHost" }] : /* istanbul ignore next */ []));
|
|
1900
1899
|
hostElement = inject(ElementRef);
|
|
1901
1900
|
engine = inject(PRAXIS_CHART_ENGINE);
|
|
1902
1901
|
transformer = inject(PraxisChartDataTransformerService);
|
|
@@ -1905,17 +1904,17 @@ class PraxisChartComponent {
|
|
|
1905
1904
|
i18n = inject(PraxisI18nService);
|
|
1906
1905
|
settingsPanel = inject(SETTINGS_PANEL_BRIDGE, { optional: true });
|
|
1907
1906
|
destroyRef = inject(DestroyRef);
|
|
1908
|
-
resizeObserver = signal(null, ...(ngDevMode ? [{ debugName: "resizeObserver" }] : []));
|
|
1909
|
-
shellObserver = signal(null, ...(ngDevMode ? [{ debugName: "shellObserver" }] : []));
|
|
1910
|
-
currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : []));
|
|
1911
|
-
remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : []));
|
|
1912
|
-
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
|
|
1913
|
-
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
|
|
1914
|
-
runtimeChartDocument = signal(null, ...(ngDevMode ? [{ debugName: "runtimeChartDocument" }] : []));
|
|
1915
|
-
mappedRuntimeConfig = signal(null, ...(ngDevMode ? [{ debugName: "mappedRuntimeConfig" }] : []));
|
|
1916
|
-
chartDocumentMappingError = signal(null, ...(ngDevMode ? [{ debugName: "chartDocumentMappingError" }] : []));
|
|
1917
|
-
fillContainerHeight = signal(false, ...(ngDevMode ? [{ debugName: "fillContainerHeight" }] : []));
|
|
1918
|
-
renderAttempt = signal(0, ...(ngDevMode ? [{ debugName: "renderAttempt" }] : []));
|
|
1907
|
+
resizeObserver = signal(null, ...(ngDevMode ? [{ debugName: "resizeObserver" }] : /* istanbul ignore next */ []));
|
|
1908
|
+
shellObserver = signal(null, ...(ngDevMode ? [{ debugName: "shellObserver" }] : /* istanbul ignore next */ []));
|
|
1909
|
+
currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : /* istanbul ignore next */ []));
|
|
1910
|
+
remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : /* istanbul ignore next */ []));
|
|
1911
|
+
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : /* istanbul ignore next */ []));
|
|
1912
|
+
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : /* istanbul ignore next */ []));
|
|
1913
|
+
runtimeChartDocument = signal(null, ...(ngDevMode ? [{ debugName: "runtimeChartDocument" }] : /* istanbul ignore next */ []));
|
|
1914
|
+
mappedRuntimeConfig = signal(null, ...(ngDevMode ? [{ debugName: "mappedRuntimeConfig" }] : /* istanbul ignore next */ []));
|
|
1915
|
+
chartDocumentMappingError = signal(null, ...(ngDevMode ? [{ debugName: "chartDocumentMappingError" }] : /* istanbul ignore next */ []));
|
|
1916
|
+
fillContainerHeight = signal(false, ...(ngDevMode ? [{ debugName: "fillContainerHeight" }] : /* istanbul ignore next */ []));
|
|
1917
|
+
renderAttempt = signal(0, ...(ngDevMode ? [{ debugName: "renderAttempt" }] : /* istanbul ignore next */ []));
|
|
1919
1918
|
resizeFrameId = null;
|
|
1920
1919
|
renderFrameId = null;
|
|
1921
1920
|
deferredRenderAttempts = 0;
|
|
@@ -1935,7 +1934,7 @@ class PraxisChartComponent {
|
|
|
1935
1934
|
return base;
|
|
1936
1935
|
}
|
|
1937
1936
|
return mergeRemoteQueryContext(base, runtimeQueryContext, runtimeFilters);
|
|
1938
|
-
}, ...(ngDevMode ? [{ debugName: "effectiveConfig" }] : []));
|
|
1937
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveConfig" }] : /* istanbul ignore next */ []));
|
|
1939
1938
|
resolvedData = computed(() => {
|
|
1940
1939
|
const inputData = this.data();
|
|
1941
1940
|
if (inputData !== null && inputData !== undefined) {
|
|
@@ -1950,7 +1949,7 @@ class PraxisChartComponent {
|
|
|
1950
1949
|
return dataSource.items ?? [];
|
|
1951
1950
|
}
|
|
1952
1951
|
return [];
|
|
1953
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedData" }] : []));
|
|
1952
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedData" }] : /* istanbul ignore next */ []));
|
|
1954
1953
|
resolvedHeight = computed(() => {
|
|
1955
1954
|
const sizing = this.effectiveConfig().sizing;
|
|
1956
1955
|
const mode = sizing?.mode ?? (sizing?.height !== undefined ? 'fixed' : undefined);
|
|
@@ -1968,21 +1967,21 @@ class PraxisChartComponent {
|
|
|
1968
1967
|
if (typeof value === 'number')
|
|
1969
1968
|
return `${value}px`;
|
|
1970
1969
|
return value || 'var(--praxis-chart-runtime-height, 320px)';
|
|
1971
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : []));
|
|
1970
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : /* istanbul ignore next */ []));
|
|
1972
1971
|
resolvedMinHeight = computed(() => {
|
|
1973
1972
|
const sizing = this.effectiveConfig().sizing;
|
|
1974
1973
|
return this.toCssSize(sizing?.minHeight) ?? (this.isFillContainerMode() ? '0' : null);
|
|
1975
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedMinHeight" }] : []));
|
|
1976
|
-
resolvedMaxHeight = computed(() => this.toCssSize(this.effectiveConfig().sizing?.maxHeight) ?? null, ...(ngDevMode ? [{ debugName: "resolvedMaxHeight" }] : []));
|
|
1977
|
-
resolvedAspectRatio = computed(() => this.toCssAspectRatio(this.effectiveConfig().sizing?.aspectRatio), ...(ngDevMode ? [{ debugName: "resolvedAspectRatio" }] : []));
|
|
1974
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedMinHeight" }] : /* istanbul ignore next */ []));
|
|
1975
|
+
resolvedMaxHeight = computed(() => this.toCssSize(this.effectiveConfig().sizing?.maxHeight) ?? null, ...(ngDevMode ? [{ debugName: "resolvedMaxHeight" }] : /* istanbul ignore next */ []));
|
|
1976
|
+
resolvedAspectRatio = computed(() => this.toCssAspectRatio(this.effectiveConfig().sizing?.aspectRatio), ...(ngDevMode ? [{ debugName: "resolvedAspectRatio" }] : /* istanbul ignore next */ []));
|
|
1978
1977
|
isFillContainerMode = computed(() => {
|
|
1979
1978
|
return this.fillContainerHeight() || this.effectiveConfig().sizing?.mode === 'fill-container';
|
|
1980
|
-
}, ...(ngDevMode ? [{ debugName: "isFillContainerMode" }] : []));
|
|
1981
|
-
surfaceMode = computed(() => this.effectiveConfig().theme?.surface?.mode ?? 'auto', ...(ngDevMode ? [{ debugName: "surfaceMode" }] : []));
|
|
1982
|
-
surfaceBackground = computed(() => this.effectiveConfig().theme?.surface?.background ?? null, ...(ngDevMode ? [{ debugName: "surfaceBackground" }] : []));
|
|
1983
|
-
surfaceBorderColor = computed(() => this.effectiveConfig().theme?.surface?.borderColor ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderColor" }] : []));
|
|
1984
|
-
surfaceBorderWidth = computed(() => this.toCssSize(this.effectiveConfig().theme?.surface?.borderWidth) ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderWidth" }] : []));
|
|
1985
|
-
surfaceBorderRadius = computed(() => this.toCssSize(this.effectiveConfig().theme?.surface?.borderRadius) ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderRadius" }] : []));
|
|
1979
|
+
}, ...(ngDevMode ? [{ debugName: "isFillContainerMode" }] : /* istanbul ignore next */ []));
|
|
1980
|
+
surfaceMode = computed(() => this.effectiveConfig().theme?.surface?.mode ?? 'auto', ...(ngDevMode ? [{ debugName: "surfaceMode" }] : /* istanbul ignore next */ []));
|
|
1981
|
+
surfaceBackground = computed(() => this.effectiveConfig().theme?.surface?.background ?? null, ...(ngDevMode ? [{ debugName: "surfaceBackground" }] : /* istanbul ignore next */ []));
|
|
1982
|
+
surfaceBorderColor = computed(() => this.effectiveConfig().theme?.surface?.borderColor ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderColor" }] : /* istanbul ignore next */ []));
|
|
1983
|
+
surfaceBorderWidth = computed(() => this.toCssSize(this.effectiveConfig().theme?.surface?.borderWidth) ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderWidth" }] : /* istanbul ignore next */ []));
|
|
1984
|
+
surfaceBorderRadius = computed(() => this.toCssSize(this.effectiveConfig().theme?.surface?.borderRadius) ?? null, ...(ngDevMode ? [{ debugName: "surfaceBorderRadius" }] : /* istanbul ignore next */ []));
|
|
1986
1985
|
renderConfig = computed(() => {
|
|
1987
1986
|
const config = this.effectiveConfig();
|
|
1988
1987
|
const explicitData = this.data();
|
|
@@ -2030,10 +2029,10 @@ class PraxisChartComponent {
|
|
|
2030
2029
|
};
|
|
2031
2030
|
}
|
|
2032
2031
|
return config;
|
|
2033
|
-
}, ...(ngDevMode ? [{ debugName: "renderConfig" }] : []));
|
|
2034
|
-
loadingLabel = computed(() => this.i18n.resolve(this.renderConfig().state?.loadingLabel), ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
|
|
2035
|
-
emptyTitle = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.title), ...(ngDevMode ? [{ debugName: "emptyTitle" }] : []));
|
|
2036
|
-
emptyDescription = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.description), ...(ngDevMode ? [{ debugName: "emptyDescription" }] : []));
|
|
2032
|
+
}, ...(ngDevMode ? [{ debugName: "renderConfig" }] : /* istanbul ignore next */ []));
|
|
2033
|
+
loadingLabel = computed(() => this.i18n.resolve(this.renderConfig().state?.loadingLabel), ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
|
|
2034
|
+
emptyTitle = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.title), ...(ngDevMode ? [{ debugName: "emptyTitle" }] : /* istanbul ignore next */ []));
|
|
2035
|
+
emptyDescription = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.description), ...(ngDevMode ? [{ debugName: "emptyDescription" }] : /* istanbul ignore next */ []));
|
|
2037
2036
|
errorTitle = computed(() => this.chartDocumentMappingError()
|
|
2038
2037
|
? this.i18n.resolve({
|
|
2039
2038
|
key: 'praxis.charts.runtime.invalidDocumentTitle',
|
|
@@ -2043,7 +2042,7 @@ class PraxisChartComponent {
|
|
|
2043
2042
|
|| this.i18n.resolve({
|
|
2044
2043
|
key: 'praxis.charts.runtime.remoteErrorTitle',
|
|
2045
2044
|
text: 'Chart data could not be loaded',
|
|
2046
|
-
}), ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
|
|
2045
|
+
}), ...(ngDevMode ? [{ debugName: "errorTitle" }] : /* istanbul ignore next */ []));
|
|
2047
2046
|
errorDescription = computed(() => this.chartDocumentMappingError()
|
|
2048
2047
|
? this.i18n.resolve({
|
|
2049
2048
|
key: 'praxis.charts.runtime.invalidDocumentDescription',
|
|
@@ -2053,8 +2052,8 @@ class PraxisChartComponent {
|
|
|
2053
2052
|
|| this.i18n.resolve({
|
|
2054
2053
|
key: 'praxis.charts.runtime.remoteErrorDescription',
|
|
2055
2054
|
text: 'Review the chart data source and analytics request before continuing.',
|
|
2056
|
-
}), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
|
|
2057
|
-
editChartLabel = computed(() => this.i18n.resolve({ key: 'praxis.charts.runtime.editChart', text: 'Edit chart settings' }), ...(ngDevMode ? [{ debugName: "editChartLabel" }] : []));
|
|
2055
|
+
}), ...(ngDevMode ? [{ debugName: "errorDescription" }] : /* istanbul ignore next */ []));
|
|
2056
|
+
editChartLabel = computed(() => this.i18n.resolve({ key: 'praxis.charts.runtime.editChart', text: 'Edit chart settings' }), ...(ngDevMode ? [{ debugName: "editChartLabel" }] : /* istanbul ignore next */ []));
|
|
2058
2057
|
loadState = computed(() => {
|
|
2059
2058
|
if (this.chartDocumentMappingError()) {
|
|
2060
2059
|
return 'error';
|
|
@@ -2071,7 +2070,7 @@ class PraxisChartComponent {
|
|
|
2071
2070
|
}
|
|
2072
2071
|
const transformed = this.transformer.transform(config, this.resolvedData());
|
|
2073
2072
|
return transformed.hasData ? 'ready' : 'empty';
|
|
2074
|
-
}, ...(ngDevMode ? [{ debugName: "loadState" }] : []));
|
|
2073
|
+
}, ...(ngDevMode ? [{ debugName: "loadState" }] : /* istanbul ignore next */ []));
|
|
2075
2074
|
constructor() {
|
|
2076
2075
|
afterNextRender(() => {
|
|
2077
2076
|
this.observeShellSizingContext();
|
|
@@ -2458,8 +2457,8 @@ class PraxisChartComponent {
|
|
|
2458
2457
|
}
|
|
2459
2458
|
this.resizeFrameId = null;
|
|
2460
2459
|
}
|
|
2461
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2462
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2460
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2461
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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 }, chartDocument: { classPropertyName: "chartDocument", publicName: "chartDocument", isSignal: true, isRequired: false, transformFunction: null }, filterCriteria: { classPropertyName: "filterCriteria", publicName: "filterCriteria", isSignal: true, isRequired: false, transformFunction: null }, queryContext: { classPropertyName: "queryContext", publicName: "queryContext", isSignal: true, isRequired: false, transformFunction: null }, remoteDataResolver: { classPropertyName: "remoteDataResolver", publicName: "remoteDataResolver", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null }, availableResources: { classPropertyName: "availableResources", publicName: "availableResources", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null }, availableTargets: { classPropertyName: "availableTargets", publicName: "availableTargets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", selectionChange: "selectionChange", crossFilter: "crossFilter", queryRequest: "queryRequest", loadStateChange: "loadStateChange", chartDocumentApplied: "chartDocumentApplied", chartDocumentSaved: "chartDocumentSaved" }, providers: [
|
|
2463
2462
|
EChartsEngineAdapter,
|
|
2464
2463
|
{
|
|
2465
2464
|
provide: PRAXIS_CHART_ENGINE,
|
|
@@ -2539,11 +2538,11 @@ class PraxisChartComponent {
|
|
|
2539
2538
|
<div #chartHost class="praxis-chart-host"></div>
|
|
2540
2539
|
}
|
|
2541
2540
|
</section>
|
|
2542
|
-
`, isInline: true, styles: [":host{display:block;height:100%;min-width:0}:host-context(.pdx-shell.no-shell) .praxis-chart-shell,:host-context(.pdx-shell.body-fill) .praxis-chart-shell,:host-context(.pdx-shell.expanded) .praxis-chart-shell,:host-context(.pdx-shell.fullscreen) .praxis-chart-shell{height:100%!important}.praxis-chart-shell{position:relative;width:100%;height:100%;min-height:240px;border-radius:var(--praxis-chart-config-surface-radius, var(--praxis-chart-surface-radius, 8px));overflow:hidden;background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-surface-bg, var(--md-sys-color-surface-container-lowest, #fff)) );border-color:var( --praxis-chart-config-surface-border, var( --praxis-chart-surface-border, color-mix(in srgb, var(--md-sys-color-outline, #c5c7ce) 44%, transparent) ) );border-style:solid;border-width:var(--praxis-chart-config-surface-border-width, 1px)}.praxis-chart-shell-fill-container{min-height:0}:host-context(.pdx-shell) .praxis-chart-shell:not(.praxis-chart-shell-contained),.praxis-chart-shell-embedded{border-width:var( --praxis-chart-config-surface-border-width, var(--praxis-chart-embedded-surface-border-width, 0) );border-radius:var( --praxis-chart-config-surface-radius, var(--praxis-chart-embedded-surface-radius, 0) );background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-embedded-surface-bg, transparent) )}.praxis-chart-settings-trigger{position:absolute;top:10px;right:10px;z-index:3;background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 88%,rgba(18,99,180,.12));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:min(240px,100%);display:grid;align-content:center;justify-items:center;gap:14px;padding:24px;text-align:center}.praxis-chart-state-copy{display:grid;gap:6px;justify-items: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-loading-hero{width:min(100%,320px);display:grid;gap:14px}.praxis-chart-loading-summary{display:flex;gap:8px;justify-content:center}.praxis-chart-loading-chip,.praxis-chart-loading-bar{border-radius:999px;background:linear-gradient(90deg,#1263b414,#1263b438,#1263b414);background-size:200% 100%;animation:praxis-chart-loading-wave 1.2s ease-in-out infinite}.praxis-chart-loading-chip{display:block;width:104px;height:12px}.praxis-chart-loading-chip--short{width:64px}.praxis-chart-loading-plot{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));align-items:end;gap:10px;height:108px;padding:12px 8px 4px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container, #eef3f8) 72%,transparent);border:1px solid rgba(18,99,180,.08)}.praxis-chart-loading-bar{display:block;width:100%}.praxis-chart-loading-bar--1{height:32%}.praxis-chart-loading-bar--2{height:68%}.praxis-chart-loading-bar--3{height:48%}.praxis-chart-loading-bar--4{height:78%}.praxis-chart-loading-bar--5{height:56%}@keyframes praxis-chart-loading-wave{0%{background-position:100% 0}to{background-position:-100% 0}}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2541
|
+
`, isInline: true, styles: [":host{display:block;height:100%;min-width:0}:host-context(.pdx-shell.no-shell) .praxis-chart-shell,:host-context(.pdx-shell.body-fill) .praxis-chart-shell,:host-context(.pdx-shell.expanded) .praxis-chart-shell,:host-context(.pdx-shell.fullscreen) .praxis-chart-shell{height:100%!important}.praxis-chart-shell{position:relative;width:100%;height:100%;min-height:240px;border-radius:var(--praxis-chart-config-surface-radius, var(--praxis-chart-surface-radius, 8px));overflow:hidden;background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-surface-bg, var(--md-sys-color-surface-container-lowest, #fff)) );border-color:var( --praxis-chart-config-surface-border, var( --praxis-chart-surface-border, color-mix(in srgb, var(--md-sys-color-outline, #c5c7ce) 44%, transparent) ) );border-style:solid;border-width:var(--praxis-chart-config-surface-border-width, 1px)}.praxis-chart-shell-fill-container{min-height:0}:host-context(.pdx-shell) .praxis-chart-shell:not(.praxis-chart-shell-contained),.praxis-chart-shell-embedded{border-width:var( --praxis-chart-config-surface-border-width, var(--praxis-chart-embedded-surface-border-width, 0) );border-radius:var( --praxis-chart-config-surface-radius, var(--praxis-chart-embedded-surface-radius, 0) );background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-embedded-surface-bg, transparent) )}.praxis-chart-settings-trigger{position:absolute;top:10px;right:10px;z-index:3;background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 88%,rgba(18,99,180,.12));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:min(240px,100%);display:grid;align-content:center;justify-items:center;gap:14px;padding:24px;text-align:center}.praxis-chart-state-copy{display:grid;gap:6px;justify-items: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-loading-hero{width:min(100%,320px);display:grid;gap:14px}.praxis-chart-loading-summary{display:flex;gap:8px;justify-content:center}.praxis-chart-loading-chip,.praxis-chart-loading-bar{border-radius:999px;background:linear-gradient(90deg,#1263b414,#1263b438,#1263b414);background-size:200% 100%;animation:praxis-chart-loading-wave 1.2s ease-in-out infinite}.praxis-chart-loading-chip{display:block;width:104px;height:12px}.praxis-chart-loading-chip--short{width:64px}.praxis-chart-loading-plot{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));align-items:end;gap:10px;height:108px;padding:12px 8px 4px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container, #eef3f8) 72%,transparent);border:1px solid rgba(18,99,180,.08)}.praxis-chart-loading-bar{display:block;width:100%}.praxis-chart-loading-bar--1{height:32%}.praxis-chart-loading-bar--2{height:68%}.praxis-chart-loading-bar--3{height:48%}.praxis-chart-loading-bar--4{height:78%}.praxis-chart-loading-bar--5{height:56%}@keyframes praxis-chart-loading-wave{0%{background-position:100% 0}to{background-position:-100% 0}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2543
2542
|
}
|
|
2544
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2543
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartComponent, decorators: [{
|
|
2545
2544
|
type: Component,
|
|
2546
|
-
args: [{ selector: 'praxis-chart', standalone: true, imports: [
|
|
2545
|
+
args: [{ selector: 'praxis-chart', standalone: true, imports: [MatButtonModule, MatIconModule, MatTooltipModule], providers: [
|
|
2547
2546
|
EChartsEngineAdapter,
|
|
2548
2547
|
{
|
|
2549
2548
|
provide: PRAXIS_CHART_ENGINE,
|
|
@@ -2751,18 +2750,18 @@ const PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH = {
|
|
|
2751
2750
|
};
|
|
2752
2751
|
|
|
2753
2752
|
class PraxisChartDrilldownPanelComponent {
|
|
2754
|
-
title = input('Detalhamento por segmento', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
2755
|
-
selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
2753
|
+
title = input('Detalhamento por segmento', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2754
|
+
selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
2756
2755
|
activeCategory = computed(() => {
|
|
2757
2756
|
const raw = this.selection()?.category;
|
|
2758
2757
|
return typeof raw === 'string' ? raw : null;
|
|
2759
|
-
}, ...(ngDevMode ? [{ debugName: "activeCategory" }] : []));
|
|
2758
|
+
}, ...(ngDevMode ? [{ debugName: "activeCategory" }] : /* istanbul ignore next */ []));
|
|
2760
2759
|
detailData = computed(() => {
|
|
2761
2760
|
const category = this.activeCategory();
|
|
2762
2761
|
if (!category)
|
|
2763
2762
|
return [];
|
|
2764
2763
|
return PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH[category] ?? [];
|
|
2765
|
-
}, ...(ngDevMode ? [{ debugName: "detailData" }] : []));
|
|
2764
|
+
}, ...(ngDevMode ? [{ debugName: "detailData" }] : /* istanbul ignore next */ []));
|
|
2766
2765
|
detailChartConfig = computed(() => {
|
|
2767
2766
|
const category = this.activeCategory();
|
|
2768
2767
|
return {
|
|
@@ -2787,9 +2786,9 @@ class PraxisChartDrilldownPanelComponent {
|
|
|
2787
2786
|
palette: ['#1263b4', '#2b8a3e', '#f08c00', '#7b61ff'],
|
|
2788
2787
|
},
|
|
2789
2788
|
};
|
|
2790
|
-
}, ...(ngDevMode ? [{ debugName: "detailChartConfig" }] : []));
|
|
2791
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2792
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2789
|
+
}, ...(ngDevMode ? [{ debugName: "detailChartConfig" }] : /* istanbul ignore next */ []));
|
|
2790
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartDrilldownPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2791
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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: `
|
|
2793
2792
|
<section class="drilldown-shell">
|
|
2794
2793
|
<header class="drilldown-header">
|
|
2795
2794
|
<p class="drilldown-eyebrow">Drill-down local</p>
|
|
@@ -2806,11 +2805,11 @@ class PraxisChartDrilldownPanelComponent {
|
|
|
2806
2805
|
[data]="detailData()"
|
|
2807
2806
|
></praxis-chart>
|
|
2808
2807
|
</section>
|
|
2809
|
-
`, 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: "
|
|
2808
|
+
`, 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: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "queryContext", "remoteDataResolver", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "selectionChange", "crossFilter", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2810
2809
|
}
|
|
2811
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2810
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartDrilldownPanelComponent, decorators: [{
|
|
2812
2811
|
type: Component,
|
|
2813
|
-
args: [{ selector: 'praxis-chart-drilldown-panel', standalone: true, imports: [
|
|
2812
|
+
args: [{ selector: 'praxis-chart-drilldown-panel', standalone: true, imports: [PraxisChartComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2814
2813
|
<section class="drilldown-shell">
|
|
2815
2814
|
<header class="drilldown-header">
|
|
2816
2815
|
<p class="drilldown-eyebrow">Drill-down local</p>
|
|
@@ -2831,11 +2830,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
2831
2830
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }] } });
|
|
2832
2831
|
|
|
2833
2832
|
class PraxisChartStateProbeComponent {
|
|
2834
|
-
title = input('Chart Runtime Probe', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
2835
|
-
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
2836
|
-
serializedValue = computed(() => JSON.stringify(this.value(), null, 2), ...(ngDevMode ? [{ debugName: "serializedValue" }] : []));
|
|
2837
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2838
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2833
|
+
title = input('Chart Runtime Probe', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2834
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
2835
|
+
serializedValue = computed(() => JSON.stringify(this.value(), null, 2), ...(ngDevMode ? [{ debugName: "serializedValue" }] : /* istanbul ignore next */ []));
|
|
2836
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartStateProbeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2837
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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: `
|
|
2839
2838
|
<section class="probe-shell">
|
|
2840
2839
|
<header class="probe-header">
|
|
2841
2840
|
<p class="probe-eyebrow">Probe</p>
|
|
@@ -2848,11 +2847,11 @@ class PraxisChartStateProbeComponent {
|
|
|
2848
2847
|
<pre>{{ serializedValue() }}</pre>
|
|
2849
2848
|
}
|
|
2850
2849
|
</section>
|
|
2851
|
-
`, 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"],
|
|
2850
|
+
`, 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"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2852
2851
|
}
|
|
2853
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2852
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartStateProbeComponent, decorators: [{
|
|
2854
2853
|
type: Component,
|
|
2855
|
-
args: [{ selector: 'praxis-chart-state-probe', standalone: true, imports: [
|
|
2854
|
+
args: [{ selector: 'praxis-chart-state-probe', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2856
2855
|
<section class="probe-shell">
|
|
2857
2856
|
<header class="probe-header">
|
|
2858
2857
|
<p class="probe-eyebrow">Probe</p>
|
|
@@ -3244,10 +3243,10 @@ class ChartEditorDefaultsService {
|
|
|
3244
3243
|
},
|
|
3245
3244
|
};
|
|
3246
3245
|
}
|
|
3247
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3248
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
3246
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorDefaultsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3247
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorDefaultsService, providedIn: 'root' });
|
|
3249
3248
|
}
|
|
3250
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3249
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorDefaultsService, decorators: [{
|
|
3251
3250
|
type: Injectable,
|
|
3252
3251
|
args: [{ providedIn: 'root' }]
|
|
3253
3252
|
}] });
|
|
@@ -3295,21 +3294,21 @@ class ChartEditorPreviewMapperService {
|
|
|
3295
3294
|
return row;
|
|
3296
3295
|
});
|
|
3297
3296
|
}
|
|
3298
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3299
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
3297
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorPreviewMapperService, deps: [{ token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3298
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorPreviewMapperService, providedIn: 'root' });
|
|
3300
3299
|
}
|
|
3301
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3300
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ChartEditorPreviewMapperService, decorators: [{
|
|
3302
3301
|
type: Injectable,
|
|
3303
3302
|
args: [{ providedIn: 'root' }]
|
|
3304
3303
|
}], ctorParameters: () => [{ type: PraxisChartCanonicalContractMapperService }] });
|
|
3305
3304
|
|
|
3306
3305
|
class PraxisChartConfigEditor {
|
|
3307
|
-
documentInput = input(null, ...(ngDevMode ?
|
|
3308
|
-
modeInput = input('edit', ...(ngDevMode ?
|
|
3309
|
-
readonlyInput = input(false, ...(ngDevMode ?
|
|
3310
|
-
availableResourcesInput = input([], ...(ngDevMode ?
|
|
3311
|
-
availableFieldsInput = input([], ...(ngDevMode ?
|
|
3312
|
-
availableTargetsInput = input([], ...(ngDevMode ?
|
|
3306
|
+
documentInput = input(null, { ...(ngDevMode ? { debugName: "documentInput" } : /* istanbul ignore next */ {}), alias: 'document' });
|
|
3307
|
+
modeInput = input('edit', { ...(ngDevMode ? { debugName: "modeInput" } : /* istanbul ignore next */ {}), alias: 'mode' });
|
|
3308
|
+
readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly' });
|
|
3309
|
+
availableResourcesInput = input([], { ...(ngDevMode ? { debugName: "availableResourcesInput" } : /* istanbul ignore next */ {}), alias: 'availableResources' });
|
|
3310
|
+
availableFieldsInput = input([], { ...(ngDevMode ? { debugName: "availableFieldsInput" } : /* istanbul ignore next */ {}), alias: 'availableFields' });
|
|
3311
|
+
availableTargetsInput = input([], { ...(ngDevMode ? { debugName: "availableTargetsInput" } : /* istanbul ignore next */ {}), alias: 'availableTargets' });
|
|
3313
3312
|
apply = output();
|
|
3314
3313
|
save = output();
|
|
3315
3314
|
resetChange = output();
|
|
@@ -3353,34 +3352,34 @@ class PraxisChartConfigEditor {
|
|
|
3353
3352
|
themeVariants = ['default', 'compact', 'executive'];
|
|
3354
3353
|
paletteModes = ['token', 'custom'];
|
|
3355
3354
|
paletteTokens = Object.keys(PRAXIS_CHART_PALETTE_TOKENS);
|
|
3356
|
-
activeSection = signal('general', ...(ngDevMode ? [{ debugName: "activeSection" }] : []));
|
|
3355
|
+
activeSection = signal('general', ...(ngDevMode ? [{ debugName: "activeSection" }] : /* istanbul ignore next */ []));
|
|
3357
3356
|
injectedData = inject(SETTINGS_PANEL_DATA, { optional: true });
|
|
3358
3357
|
defaults = inject(ChartEditorDefaultsService);
|
|
3359
3358
|
normalizer = inject(ChartContractNormalizerService);
|
|
3360
3359
|
validator = inject(ChartContractValidationService);
|
|
3361
3360
|
previewMapper = inject(ChartEditorPreviewMapperService);
|
|
3362
3361
|
i18n = inject(PraxisI18nService);
|
|
3363
|
-
currentDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "currentDocument" }] : []));
|
|
3364
|
-
initialDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "initialDocument" }] : []));
|
|
3362
|
+
currentDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "currentDocument" }] : /* istanbul ignore next */ []));
|
|
3363
|
+
initialDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "initialDocument" }] : /* istanbul ignore next */ []));
|
|
3365
3364
|
lastExternalSignature = null;
|
|
3366
|
-
normalizedDocument = computed(() => this.normalizer.normalize(this.currentDocument()), ...(ngDevMode ? [{ debugName: "normalizedDocument" }] : []));
|
|
3367
|
-
validation = computed(() => this.validator.validate(this.normalizedDocument()), ...(ngDevMode ? [{ debugName: "validation" }] : []));
|
|
3368
|
-
issues = computed(() => this.validation().issues, ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
3365
|
+
normalizedDocument = computed(() => this.normalizer.normalize(this.currentDocument()), ...(ngDevMode ? [{ debugName: "normalizedDocument" }] : /* istanbul ignore next */ []));
|
|
3366
|
+
validation = computed(() => this.validator.validate(this.normalizedDocument()), ...(ngDevMode ? [{ debugName: "validation" }] : /* istanbul ignore next */ []));
|
|
3367
|
+
issues = computed(() => this.validation().issues, ...(ngDevMode ? [{ debugName: "issues" }] : /* istanbul ignore next */ []));
|
|
3369
3368
|
availableResources = computed(() => this.availableResourcesInput().length
|
|
3370
3369
|
? this.availableResourcesInput()
|
|
3371
|
-
: (this.injectedData?.availableResources ?? []), ...(ngDevMode ? [{ debugName: "availableResources" }] : []));
|
|
3370
|
+
: (this.injectedData?.availableResources ?? []), ...(ngDevMode ? [{ debugName: "availableResources" }] : /* istanbul ignore next */ []));
|
|
3372
3371
|
availableFields = computed(() => this.availableFieldsInput().length
|
|
3373
3372
|
? this.availableFieldsInput()
|
|
3374
|
-
: (this.injectedData?.availableFields ?? []), ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
3373
|
+
: (this.injectedData?.availableFields ?? []), ...(ngDevMode ? [{ debugName: "availableFields" }] : /* istanbul ignore next */ []));
|
|
3375
3374
|
availableTargets = computed(() => this.availableTargetsInput().length
|
|
3376
3375
|
? this.availableTargetsInput()
|
|
3377
|
-
: (this.injectedData?.availableTargets ?? []), ...(ngDevMode ? [{ debugName: "availableTargets" }] : []));
|
|
3376
|
+
: (this.injectedData?.availableTargets ?? []), ...(ngDevMode ? [{ debugName: "availableTargets" }] : /* istanbul ignore next */ []));
|
|
3378
3377
|
preview = computed(() => {
|
|
3379
3378
|
if (!this.validation().valid) {
|
|
3380
3379
|
return null;
|
|
3381
3380
|
}
|
|
3382
3381
|
return this.previewMapper.build(this.normalizedDocument());
|
|
3383
|
-
}, ...(ngDevMode ? [{ debugName: "preview" }] : []));
|
|
3382
|
+
}, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
|
|
3384
3383
|
constructor() {
|
|
3385
3384
|
effect(() => {
|
|
3386
3385
|
const externalDocument = this.documentInput()
|
|
@@ -4090,13 +4089,12 @@ class PraxisChartConfigEditor {
|
|
|
4090
4089
|
this.isValid$.next(this.validation().valid);
|
|
4091
4090
|
this.documentChange.emit(structuredClone(this.normalizedDocument()));
|
|
4092
4091
|
}
|
|
4093
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4094
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartConfigEditor, isStandalone: true, selector: "praxis-chart-config-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, modeInput: { classPropertyName: "modeInput", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, availableResourcesInput: { classPropertyName: "availableResourcesInput", publicName: "availableResources", isSignal: true, isRequired: false, transformFunction: null }, availableFieldsInput: { classPropertyName: "availableFieldsInput", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null }, availableTargetsInput: { classPropertyName: "availableTargetsInput", publicName: "availableTargets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", save: "save", resetChange: "resetChange", documentChange: "documentChange" }, providers: [providePraxisChartsI18n()], ngImport: i0, template: "<div class=\"editor-shell\">\n <div class=\"editor-nav\">\n @for (section of sections; track section.id) {\n <button\n mat-stroked-button\n type=\"button\"\n [class.active]=\"activeSection() === section.id\"\n (click)=\"setSection(section.id)\"\n >\n {{ t(section.labelKey, section.fallback) }}\n </button>\n }\n </div>\n\n <div class=\"editor-layout\">\n <div class=\"editor-form\">\n <mat-card class=\"editor-card\">\n <mat-card-content>\n @switch (activeSection()) {\n @case ('general') {\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.chartId', 'Chart ID') }}</mat-label>\n <input matInput [ngModel]=\"doc().chartId || ''\" (ngModelChange)=\"setChartId($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.kind', 'Kind') }}</mat-label>\n <mat-select [ngModel]=\"doc().kind\" (ngModelChange)=\"setKind($event)\" [disabled]=\"isReadonly()\">\n @for (kind of chartKinds; track kind) {\n <mat-option [value]=\"kind\">\n {{ t('praxis.charts.editor.kind.' + kind, kind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.title', 'Title') }}</mat-label>\n <input matInput data-testid=\"chart-editor-title-input\" [ngModel]=\"titleValue()\" (ngModelChange)=\"setTitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.subtitle', 'Subtitle') }}</mat-label>\n <input matInput [ngModel]=\"subtitleValue()\" (ngModelChange)=\"setSubtitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sizingMode', 'Sizing') }}</mat-label>\n <mat-select [ngModel]=\"sizingModeValue()\" (ngModelChange)=\"setSizingMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of sizingModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.sizing.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n <mat-hint>{{ t('praxis.charts.editor.hint.sizingMode', 'Use fill-container only when the host widget provides a defined body height.') }}</mat-hint>\n </mat-form-field>\n\n @if (sizingModeValue() === 'fixed') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.height', 'Height') }}</mat-label>\n <input matInput [ngModel]=\"heightValue()\" (ngModelChange)=\"setSizingHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.height', 'Numbers are saved as pixels; CSS lengths such as 20rem are also accepted.') }}</mat-hint>\n </mat-form-field>\n }\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.minHeight', 'Minimum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMinHeightValue()\" (ngModelChange)=\"setSizingMinHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.minHeight', 'Set a readable minimum for compact dashboard widgets.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.maxHeight', 'Maximum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMaxHeightValue()\" (ngModelChange)=\"setSizingMaxHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.maxHeight', 'Leave empty unless the chart must stop growing inside a flexible layout.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.aspectRatio', 'Aspect ratio') }}</mat-label>\n <input matInput [ngModel]=\"sizingAspectRatioValue()\" (ngModelChange)=\"setSizingAspectRatio($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.aspectRatio', 'Optional. Use values such as 1.777 or 16 / 9.') }}</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('data') {\n <div class=\"editor-stack\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sourceKind', 'Source') }}</mat-label>\n <mat-select [ngModel]=\"doc().source.kind\" (ngModelChange)=\"setSourceKind($event)\" [disabled]=\"isReadonly()\">\n @for (sourceKind of sourceKinds; track sourceKind) {\n <mat-option [value]=\"sourceKind\">\n {{ t('praxis.charts.editor.sourceKind.' + (sourceKind === 'praxis.stats' ? 'praxisStats' : 'derived'), sourceKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (doc().source.kind === 'praxis.stats') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.resource', 'Resource') }}</mat-label>\n @if (resourceOptions().length) {\n <mat-select [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\">\n @for (resource of resourceOptions(); track resource.id) {\n <mat-option [value]=\"resource.path\">{{ resource.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.operation', 'Operation') }}</mat-label>\n <mat-select\n [ngModel]=\"doc().source.operation || 'group-by'\"\n (ngModelChange)=\"setOperation($event)\"\n [disabled]=\"isReadonly()\"\n >\n @for (operation of operations; track operation) {\n <mat-option [value]=\"operation\">\n {{ t('praxis.charts.editor.operation.' + (operation === 'group-by' ? 'groupBy' : operation), operation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n @if (showTimeseriesControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.timeseriesTitle', 'Timeseries options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.granularity', 'Granularity') }}</mat-label>\n <mat-select [ngModel]=\"granularityValue()\" (ngModelChange)=\"setGranularity($event)\" [disabled]=\"isReadonly()\">\n @for (granularity of timeGranularities; track granularity) {\n <mat-option [value]=\"granularity\">\n {{ t('praxis.charts.editor.granularity.' + granularity, granularity) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-slide-toggle\n [ngModel]=\"fillGapsValue()\"\n (ngModelChange)=\"setFillGaps($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.fillGaps', 'Fill missing intervals') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showDistributionControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.distributionTitle', 'Distribution options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.distributionMode', 'Distribution mode') }}</mat-label>\n <mat-select [ngModel]=\"distributionModeValue()\" (ngModelChange)=\"setDistributionMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of distributionModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.distributionMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketSize', 'Bucket size') }}</mat-label>\n <input matInput [ngModel]=\"bucketSizeValue()\" (ngModelChange)=\"setBucketSize($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketCount', 'Bucket count') }}</mat-label>\n <input matInput [ngModel]=\"bucketCountValue()\" (ngModelChange)=\"setBucketCount($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n }\n </div>\n }\n\n @case ('motion') {\n <div class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"normalizedDocument().motion?.enabled !== false\"\n (ngModelChange)=\"setMotionEnabled($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.motionEnabled', 'Enable animations') }}\n </mat-slide-toggle>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.motionPreset', 'Motion preset') }}</mat-label>\n <mat-select\n [ngModel]=\"normalizedDocument().motion?.preset || 'standard'\"\n (ngModelChange)=\"setMotionPreset($event)\"\n [disabled]=\"isReadonly() || normalizedDocument().motion?.enabled === false\"\n >\n @for (preset of motionPresets; track preset) {\n <mat-option [value]=\"preset\">\n {{ t('praxis.charts.editor.motionPreset.' + preset, preset) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n @case ('appearance') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.featuresTitle', 'Display features') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('legend')\"\n (ngModelChange)=\"setFeatureEnabled('legend', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.legendEnabled', 'Show legend') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('labels')\"\n (ngModelChange)=\"setFeatureEnabled('labels', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.labelsEnabled', 'Show labels') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('tooltip')\"\n (ngModelChange)=\"setFeatureEnabled('tooltip', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.tooltipEnabled', 'Show tooltip') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.paletteTitle', 'Palette') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.themeVariant', 'Theme variant') }}</mat-label>\n <mat-select [ngModel]=\"themeVariantValue()\" (ngModelChange)=\"setThemeVariant($event)\" [disabled]=\"isReadonly()\">\n <mat-option [value]=\"''\">\n {{ t('praxis.charts.editor.themeVariant.none', 'No variant') }}\n </mat-option>\n @for (variant of themeVariants; track variant) {\n <mat-option [value]=\"variant\">\n {{ t('praxis.charts.editor.themeVariant.' + variant, variant) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteMode', 'Palette mode') }}</mat-label>\n <mat-select [ngModel]=\"paletteModeValue()\" (ngModelChange)=\"setPaletteMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of paletteModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.paletteMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (paletteModeValue() === 'token') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteToken', 'Palette token') }}</mat-label>\n <mat-select [ngModel]=\"paletteTokenValue()\" (ngModelChange)=\"setPaletteToken($event)\" [disabled]=\"isReadonly()\">\n @for (token of paletteTokens; track token) {\n <mat-option [value]=\"token\">\n {{ t('praxis.charts.editor.paletteToken.' + token, token) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.palette', 'Palette colors') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"paletteValue()\"\n (ngModelChange)=\"setPalette($event)\"\n [disabled]=\"isReadonly()\"\n ></textarea>\n </mat-form-field>\n }\n\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.paletteHint', 'Use a registered token or comma-separated colors to persist theme.palette in the canonical contract.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.surfaceTitle', 'Surface') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.surfaceMode', 'Surface mode') }}</mat-label>\n <mat-select [ngModel]=\"surfaceModeValue()\" (ngModelChange)=\"setSurfaceMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of surfaceModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.surface.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.surfaceHint', 'Use embedded for dashboard widgets and contained only when the chart must own its visual surface.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.statesTitle', 'State messages') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyTitle', 'Empty title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('empty')\" (ngModelChange)=\"setStateTitle('empty', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyDescription', 'Empty description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('empty')\" (ngModelChange)=\"setStateDescription('empty', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingTitle', 'Loading title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('loading')\" (ngModelChange)=\"setStateTitle('loading', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingDescription', 'Loading description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('loading')\" (ngModelChange)=\"setStateDescription('loading', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorTitle', 'Error title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('error')\" (ngModelChange)=\"setStateTitle('error', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorDescription', 'Error description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('error')\" (ngModelChange)=\"setStateDescription('error', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('analytics') {\n <div class=\"editor-stack\">\n @if (showComboPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.comboTitle', 'Combo guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.comboHint', 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showPieDonutPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.pieDonutTitle', 'Composition guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.pieDonutHint', 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showScatterPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.scatterTitle', 'Scatter guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.scatterHint', 'Scatter charts use the first dimension as X and the first metric as Y, so keep both fields mapped.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.dimensionsTitle', 'Dimensions') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (dimension of dimensions(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimension', 'Dimension') }}</mat-label>\n @if (fieldOptions('dimension').length) {\n <mat-select [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('dimension'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimensionRole', 'Dimension role') }}</mat-label>\n <mat-select [ngModel]=\"dimension.role || 'category'\" (ngModelChange)=\"setDimensionRole($index, $event)\" [disabled]=\"isReadonly()\">\n @for (role of dimensionRoles; track role) {\n <mat-option [value]=\"role\">\n {{ t('praxis.charts.editor.dimensionRole.' + role, role) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeDimension($index)\" [disabled]=\"isReadonly() || dimensions().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeDimension', 'Remove dimension') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addDimension()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addDimension', 'Add dimension') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.metricsTitle', 'Metrics') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (metric of metrics(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metric', 'Metric') }}</mat-label>\n @if (fieldOptions('metric').length) {\n <mat-select [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('metric'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricLabel', 'Metric label') }}</mat-label>\n <input matInput [ngModel]=\"metric.label || ''\" (ngModelChange)=\"setMetricLabel($index, $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAggregation', 'Aggregation') }}</mat-label>\n <mat-select [ngModel]=\"metric.aggregation || 'sum'\" (ngModelChange)=\"setMetricAggregation($index, $event)\" [disabled]=\"isReadonly()\">\n @for (aggregation of metricAggregations; track aggregation) {\n <mat-option [value]=\"aggregation\">\n {{ t('praxis.charts.editor.metricAggregation.' + aggregation, aggregation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (showMetricAxisControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAxis', 'Axis') }}</mat-label>\n <mat-select [ngModel]=\"metric.axis || 'primary'\" (ngModelChange)=\"setMetricAxis($index, $event)\" [disabled]=\"isReadonly()\">\n @for (axis of metricAxes; track axis) {\n <mat-option [value]=\"axis\">\n {{ t('praxis.charts.editor.metricAxis.' + axis, axis) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (showMetricSeriesKindControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricSeriesKind', 'Series kind') }}</mat-label>\n <mat-select [ngModel]=\"metric.seriesKind || 'bar'\" (ngModelChange)=\"setMetricSeriesKind($index, $event)\" [disabled]=\"isReadonly()\">\n @for (seriesKind of metricSeriesKinds; track seriesKind) {\n <mat-option [value]=\"seriesKind\">\n {{ t('praxis.charts.editor.metricSeriesKind.' + seriesKind, seriesKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeMetric($index)\" [disabled]=\"isReadonly() || metrics().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeMetric', 'Remove metric') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addMetric()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addMetric', 'Add metric') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('events') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.pointClickTitle', 'Point click') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('pointClick')\" (ngModelChange)=\"setEventAction('pointClick', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('pointClick')\"\n (ngModelChange)=\"setEventMapping('pointClick', $event)\"\n [disabled]=\"isReadonly() || !eventAction('pointClick')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.selectionChangeTitle', 'Selection change') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('selectionChange')\" (ngModelChange)=\"setEventAction('selectionChange', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('selectionChange')\"\n (ngModelChange)=\"setEventMapping('selectionChange', $event)\"\n [disabled]=\"isReadonly() || !eventAction('selectionChange')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.drillDownTitle', 'Drill down') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('drillDown')\" (ngModelChange)=\"setEventAction('drillDown', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('drillDown')\"\n (ngModelChange)=\"setEventMapping('drillDown', $event)\"\n [disabled]=\"isReadonly() || !eventAction('drillDown')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.crossFilterTitle', 'Cross filter') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('crossFilter')\" (ngModelChange)=\"setEventAction('crossFilter', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('crossFilter')\"\n (ngModelChange)=\"setEventMapping('crossFilter', $event)\"\n [disabled]=\"isReadonly() || !eventAction('crossFilter')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('preview') {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n }\n }\n </mat-card-content>\n </mat-card>\n </div>\n\n <div class=\"editor-side\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.issues.title', 'Validation issues') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (issues().length) {\n <ul class=\"editor-issues\">\n @for (issue of issues(); track issueTrackBy($index, issue)) {\n <li class=\"editor-issue\">\n <strong>{{ issue.field }}</strong>\n <span>{{ issue.message }}</span>\n </li>\n }\n </ul>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.issues.empty', 'No issues were identified.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.preview.title', 'Chart preview') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (preview(); as chartPreview) {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n <praxis-chart [config]=\"chartPreview.config\" [data]=\"chartPreview.data\"></praxis-chart>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.preview.invalid', 'Preview is unavailable while the contract has blocking errors.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}.editor-shell{display:grid;gap:18px}.editor-nav{display:flex;gap:8px;flex-wrap:wrap}.editor-nav button.active{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 18%,transparent);color:var(--md-sys-color-primary, #1263b4)}.editor-layout{display:grid;gap:18px;grid-template-columns:minmax(0,1.35fr) minmax(320px,.9fr);align-items:start}.editor-form,.editor-side{display:grid;gap:16px}.editor-card{border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#1263b408,#1263b400)}.editor-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-stack{display:grid;gap:14px}.editor-row-card{padding:14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 54%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 92%,rgba(18,99,180,.04))}.editor-row-actions{display:flex;justify-content:flex-end}.editor-field{width:100%}.editor-issues{display:grid;gap:10px;margin:0;padding:0;list-style:none}.editor-issue{padding:12px 14px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-error, #b3261e) 8%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-error, #b3261e) 18%,transparent)}.editor-issue strong{display:block;margin-bottom:4px}.editor-caption{margin:0 0 12px;color:var(--md-sys-color-on-surface-variant, #5a5d67);font-size:.92rem}.editor-empty{padding:18px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-variant, #eceff4) 78%,transparent)}@media(max-width:960px){.editor-layout{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i3$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i3$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "queryContext", "remoteDataResolver", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "selectionChange", "crossFilter", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4092
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4093
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisChartConfigEditor, isStandalone: true, selector: "praxis-chart-config-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, modeInput: { classPropertyName: "modeInput", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, availableResourcesInput: { classPropertyName: "availableResourcesInput", publicName: "availableResources", isSignal: true, isRequired: false, transformFunction: null }, availableFieldsInput: { classPropertyName: "availableFieldsInput", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null }, availableTargetsInput: { classPropertyName: "availableTargetsInput", publicName: "availableTargets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", save: "save", resetChange: "resetChange", documentChange: "documentChange" }, providers: [providePraxisChartsI18n()], ngImport: i0, template: "<div class=\"editor-shell\">\n <div class=\"editor-nav\">\n @for (section of sections; track section.id) {\n <button\n mat-stroked-button\n type=\"button\"\n [class.active]=\"activeSection() === section.id\"\n (click)=\"setSection(section.id)\"\n >\n {{ t(section.labelKey, section.fallback) }}\n </button>\n }\n </div>\n\n <div class=\"editor-layout\">\n <div class=\"editor-form\">\n <mat-card class=\"editor-card\">\n <mat-card-content>\n @switch (activeSection()) {\n @case ('general') {\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.chartId', 'Chart ID') }}</mat-label>\n <input matInput [ngModel]=\"doc().chartId || ''\" (ngModelChange)=\"setChartId($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.kind', 'Kind') }}</mat-label>\n <mat-select [ngModel]=\"doc().kind\" (ngModelChange)=\"setKind($event)\" [disabled]=\"isReadonly()\">\n @for (kind of chartKinds; track kind) {\n <mat-option [value]=\"kind\">\n {{ t('praxis.charts.editor.kind.' + kind, kind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.title', 'Title') }}</mat-label>\n <input matInput data-testid=\"chart-editor-title-input\" [ngModel]=\"titleValue()\" (ngModelChange)=\"setTitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.subtitle', 'Subtitle') }}</mat-label>\n <input matInput [ngModel]=\"subtitleValue()\" (ngModelChange)=\"setSubtitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sizingMode', 'Sizing') }}</mat-label>\n <mat-select [ngModel]=\"sizingModeValue()\" (ngModelChange)=\"setSizingMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of sizingModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.sizing.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n <mat-hint>{{ t('praxis.charts.editor.hint.sizingMode', 'Use fill-container only when the host widget provides a defined body height.') }}</mat-hint>\n </mat-form-field>\n\n @if (sizingModeValue() === 'fixed') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.height', 'Height') }}</mat-label>\n <input matInput [ngModel]=\"heightValue()\" (ngModelChange)=\"setSizingHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.height', 'Numbers are saved as pixels; CSS lengths such as 20rem are also accepted.') }}</mat-hint>\n </mat-form-field>\n }\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.minHeight', 'Minimum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMinHeightValue()\" (ngModelChange)=\"setSizingMinHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.minHeight', 'Set a readable minimum for compact dashboard widgets.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.maxHeight', 'Maximum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMaxHeightValue()\" (ngModelChange)=\"setSizingMaxHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.maxHeight', 'Leave empty unless the chart must stop growing inside a flexible layout.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.aspectRatio', 'Aspect ratio') }}</mat-label>\n <input matInput [ngModel]=\"sizingAspectRatioValue()\" (ngModelChange)=\"setSizingAspectRatio($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.aspectRatio', 'Optional. Use values such as 1.777 or 16 / 9.') }}</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('data') {\n <div class=\"editor-stack\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sourceKind', 'Source') }}</mat-label>\n <mat-select [ngModel]=\"doc().source.kind\" (ngModelChange)=\"setSourceKind($event)\" [disabled]=\"isReadonly()\">\n @for (sourceKind of sourceKinds; track sourceKind) {\n <mat-option [value]=\"sourceKind\">\n {{ t('praxis.charts.editor.sourceKind.' + (sourceKind === 'praxis.stats' ? 'praxisStats' : 'derived'), sourceKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (doc().source.kind === 'praxis.stats') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.resource', 'Resource') }}</mat-label>\n @if (resourceOptions().length) {\n <mat-select [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\">\n @for (resource of resourceOptions(); track resource.id) {\n <mat-option [value]=\"resource.path\">{{ resource.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.operation', 'Operation') }}</mat-label>\n <mat-select\n [ngModel]=\"doc().source.operation || 'group-by'\"\n (ngModelChange)=\"setOperation($event)\"\n [disabled]=\"isReadonly()\"\n >\n @for (operation of operations; track operation) {\n <mat-option [value]=\"operation\">\n {{ t('praxis.charts.editor.operation.' + (operation === 'group-by' ? 'groupBy' : operation), operation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n @if (showTimeseriesControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.timeseriesTitle', 'Timeseries options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.granularity', 'Granularity') }}</mat-label>\n <mat-select [ngModel]=\"granularityValue()\" (ngModelChange)=\"setGranularity($event)\" [disabled]=\"isReadonly()\">\n @for (granularity of timeGranularities; track granularity) {\n <mat-option [value]=\"granularity\">\n {{ t('praxis.charts.editor.granularity.' + granularity, granularity) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-slide-toggle\n [ngModel]=\"fillGapsValue()\"\n (ngModelChange)=\"setFillGaps($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.fillGaps', 'Fill missing intervals') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showDistributionControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.distributionTitle', 'Distribution options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.distributionMode', 'Distribution mode') }}</mat-label>\n <mat-select [ngModel]=\"distributionModeValue()\" (ngModelChange)=\"setDistributionMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of distributionModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.distributionMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketSize', 'Bucket size') }}</mat-label>\n <input matInput [ngModel]=\"bucketSizeValue()\" (ngModelChange)=\"setBucketSize($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketCount', 'Bucket count') }}</mat-label>\n <input matInput [ngModel]=\"bucketCountValue()\" (ngModelChange)=\"setBucketCount($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n }\n </div>\n }\n\n @case ('motion') {\n <div class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"normalizedDocument().motion?.enabled !== false\"\n (ngModelChange)=\"setMotionEnabled($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.motionEnabled', 'Enable animations') }}\n </mat-slide-toggle>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.motionPreset', 'Motion preset') }}</mat-label>\n <mat-select\n [ngModel]=\"normalizedDocument().motion?.preset || 'standard'\"\n (ngModelChange)=\"setMotionPreset($event)\"\n [disabled]=\"isReadonly() || normalizedDocument().motion?.enabled === false\"\n >\n @for (preset of motionPresets; track preset) {\n <mat-option [value]=\"preset\">\n {{ t('praxis.charts.editor.motionPreset.' + preset, preset) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n @case ('appearance') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.featuresTitle', 'Display features') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('legend')\"\n (ngModelChange)=\"setFeatureEnabled('legend', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.legendEnabled', 'Show legend') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('labels')\"\n (ngModelChange)=\"setFeatureEnabled('labels', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.labelsEnabled', 'Show labels') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('tooltip')\"\n (ngModelChange)=\"setFeatureEnabled('tooltip', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.tooltipEnabled', 'Show tooltip') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.paletteTitle', 'Palette') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.themeVariant', 'Theme variant') }}</mat-label>\n <mat-select [ngModel]=\"themeVariantValue()\" (ngModelChange)=\"setThemeVariant($event)\" [disabled]=\"isReadonly()\">\n <mat-option [value]=\"''\">\n {{ t('praxis.charts.editor.themeVariant.none', 'No variant') }}\n </mat-option>\n @for (variant of themeVariants; track variant) {\n <mat-option [value]=\"variant\">\n {{ t('praxis.charts.editor.themeVariant.' + variant, variant) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteMode', 'Palette mode') }}</mat-label>\n <mat-select [ngModel]=\"paletteModeValue()\" (ngModelChange)=\"setPaletteMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of paletteModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.paletteMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (paletteModeValue() === 'token') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteToken', 'Palette token') }}</mat-label>\n <mat-select [ngModel]=\"paletteTokenValue()\" (ngModelChange)=\"setPaletteToken($event)\" [disabled]=\"isReadonly()\">\n @for (token of paletteTokens; track token) {\n <mat-option [value]=\"token\">\n {{ t('praxis.charts.editor.paletteToken.' + token, token) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.palette', 'Palette colors') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"paletteValue()\"\n (ngModelChange)=\"setPalette($event)\"\n [disabled]=\"isReadonly()\"\n ></textarea>\n </mat-form-field>\n }\n\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.paletteHint', 'Use a registered token or comma-separated colors to persist theme.palette in the canonical contract.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.surfaceTitle', 'Surface') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.surfaceMode', 'Surface mode') }}</mat-label>\n <mat-select [ngModel]=\"surfaceModeValue()\" (ngModelChange)=\"setSurfaceMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of surfaceModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.surface.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.surfaceHint', 'Use embedded for dashboard widgets and contained only when the chart must own its visual surface.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.statesTitle', 'State messages') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyTitle', 'Empty title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('empty')\" (ngModelChange)=\"setStateTitle('empty', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyDescription', 'Empty description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('empty')\" (ngModelChange)=\"setStateDescription('empty', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingTitle', 'Loading title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('loading')\" (ngModelChange)=\"setStateTitle('loading', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingDescription', 'Loading description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('loading')\" (ngModelChange)=\"setStateDescription('loading', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorTitle', 'Error title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('error')\" (ngModelChange)=\"setStateTitle('error', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorDescription', 'Error description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('error')\" (ngModelChange)=\"setStateDescription('error', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('analytics') {\n <div class=\"editor-stack\">\n @if (showComboPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.comboTitle', 'Combo guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.comboHint', 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showPieDonutPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.pieDonutTitle', 'Composition guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.pieDonutHint', 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showScatterPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.scatterTitle', 'Scatter guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.scatterHint', 'Scatter charts use the first dimension as X and the first metric as Y, so keep both fields mapped.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.dimensionsTitle', 'Dimensions') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (dimension of dimensions(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimension', 'Dimension') }}</mat-label>\n @if (fieldOptions('dimension').length) {\n <mat-select [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('dimension'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimensionRole', 'Dimension role') }}</mat-label>\n <mat-select [ngModel]=\"dimension.role || 'category'\" (ngModelChange)=\"setDimensionRole($index, $event)\" [disabled]=\"isReadonly()\">\n @for (role of dimensionRoles; track role) {\n <mat-option [value]=\"role\">\n {{ t('praxis.charts.editor.dimensionRole.' + role, role) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeDimension($index)\" [disabled]=\"isReadonly() || dimensions().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeDimension', 'Remove dimension') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addDimension()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addDimension', 'Add dimension') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.metricsTitle', 'Metrics') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (metric of metrics(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metric', 'Metric') }}</mat-label>\n @if (fieldOptions('metric').length) {\n <mat-select [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('metric'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricLabel', 'Metric label') }}</mat-label>\n <input matInput [ngModel]=\"metric.label || ''\" (ngModelChange)=\"setMetricLabel($index, $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAggregation', 'Aggregation') }}</mat-label>\n <mat-select [ngModel]=\"metric.aggregation || 'sum'\" (ngModelChange)=\"setMetricAggregation($index, $event)\" [disabled]=\"isReadonly()\">\n @for (aggregation of metricAggregations; track aggregation) {\n <mat-option [value]=\"aggregation\">\n {{ t('praxis.charts.editor.metricAggregation.' + aggregation, aggregation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (showMetricAxisControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAxis', 'Axis') }}</mat-label>\n <mat-select [ngModel]=\"metric.axis || 'primary'\" (ngModelChange)=\"setMetricAxis($index, $event)\" [disabled]=\"isReadonly()\">\n @for (axis of metricAxes; track axis) {\n <mat-option [value]=\"axis\">\n {{ t('praxis.charts.editor.metricAxis.' + axis, axis) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (showMetricSeriesKindControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricSeriesKind', 'Series kind') }}</mat-label>\n <mat-select [ngModel]=\"metric.seriesKind || 'bar'\" (ngModelChange)=\"setMetricSeriesKind($index, $event)\" [disabled]=\"isReadonly()\">\n @for (seriesKind of metricSeriesKinds; track seriesKind) {\n <mat-option [value]=\"seriesKind\">\n {{ t('praxis.charts.editor.metricSeriesKind.' + seriesKind, seriesKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeMetric($index)\" [disabled]=\"isReadonly() || metrics().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeMetric', 'Remove metric') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addMetric()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addMetric', 'Add metric') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('events') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.pointClickTitle', 'Point click') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('pointClick')\" (ngModelChange)=\"setEventAction('pointClick', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('pointClick')\"\n (ngModelChange)=\"setEventMapping('pointClick', $event)\"\n [disabled]=\"isReadonly() || !eventAction('pointClick')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.selectionChangeTitle', 'Selection change') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('selectionChange')\" (ngModelChange)=\"setEventAction('selectionChange', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('selectionChange')\"\n (ngModelChange)=\"setEventMapping('selectionChange', $event)\"\n [disabled]=\"isReadonly() || !eventAction('selectionChange')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.drillDownTitle', 'Drill down') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('drillDown')\" (ngModelChange)=\"setEventAction('drillDown', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('drillDown')\"\n (ngModelChange)=\"setEventMapping('drillDown', $event)\"\n [disabled]=\"isReadonly() || !eventAction('drillDown')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.crossFilterTitle', 'Cross filter') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('crossFilter')\" (ngModelChange)=\"setEventAction('crossFilter', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('crossFilter')\"\n (ngModelChange)=\"setEventMapping('crossFilter', $event)\"\n [disabled]=\"isReadonly() || !eventAction('crossFilter')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('preview') {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n }\n }\n </mat-card-content>\n </mat-card>\n </div>\n\n <div class=\"editor-side\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.issues.title', 'Validation issues') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (issues().length) {\n <ul class=\"editor-issues\">\n @for (issue of issues(); track issueTrackBy($index, issue)) {\n <li class=\"editor-issue\">\n <strong>{{ issue.field }}</strong>\n <span>{{ issue.message }}</span>\n </li>\n }\n </ul>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.issues.empty', 'No issues were identified.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.preview.title', 'Chart preview') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (preview(); as chartPreview) {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n <praxis-chart [config]=\"chartPreview.config\" [data]=\"chartPreview.data\"></praxis-chart>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.preview.invalid', 'Preview is unavailable while the contract has blocking errors.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}.editor-shell{display:grid;gap:18px}.editor-nav{display:flex;gap:8px;flex-wrap:wrap}.editor-nav button.active{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 18%,transparent);color:var(--md-sys-color-primary, #1263b4)}.editor-layout{display:grid;gap:18px;grid-template-columns:minmax(0,1.35fr) minmax(320px,.9fr);align-items:start}.editor-form,.editor-side{display:grid;gap:16px}.editor-card{border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#1263b408,#1263b400)}.editor-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-stack{display:grid;gap:14px}.editor-row-card{padding:14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 54%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 92%,rgba(18,99,180,.04))}.editor-row-actions{display:flex;justify-content:flex-end}.editor-field{width:100%}.editor-issues{display:grid;gap:10px;margin:0;padding:0;list-style:none}.editor-issue{padding:12px 14px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-error, #b3261e) 8%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-error, #b3261e) 18%,transparent)}.editor-issue strong{display:block;margin-bottom:4px}.editor-caption{margin:0 0 12px;color:var(--md-sys-color-on-surface-variant, #5a5d67);font-size:.92rem}.editor-empty{padding:18px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-variant, #eceff4) 78%,transparent)}@media(max-width:960px){.editor-layout{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i3$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i3$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "queryContext", "remoteDataResolver", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "selectionChange", "crossFilter", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4095
4094
|
}
|
|
4096
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4095
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartConfigEditor, decorators: [{
|
|
4097
4096
|
type: Component,
|
|
4098
4097
|
args: [{ selector: 'praxis-chart-config-editor', standalone: true, imports: [
|
|
4099
|
-
CommonModule,
|
|
4100
4098
|
FormsModule,
|
|
4101
4099
|
MatButtonModule,
|
|
4102
4100
|
MatCardModule,
|
|
@@ -4104,7 +4102,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4104
4102
|
MatInputModule,
|
|
4105
4103
|
MatSelectModule,
|
|
4106
4104
|
MatSlideToggleModule,
|
|
4107
|
-
PraxisChartComponent
|
|
4105
|
+
PraxisChartComponent
|
|
4108
4106
|
], providers: [providePraxisChartsI18n()], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"editor-shell\">\n <div class=\"editor-nav\">\n @for (section of sections; track section.id) {\n <button\n mat-stroked-button\n type=\"button\"\n [class.active]=\"activeSection() === section.id\"\n (click)=\"setSection(section.id)\"\n >\n {{ t(section.labelKey, section.fallback) }}\n </button>\n }\n </div>\n\n <div class=\"editor-layout\">\n <div class=\"editor-form\">\n <mat-card class=\"editor-card\">\n <mat-card-content>\n @switch (activeSection()) {\n @case ('general') {\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.chartId', 'Chart ID') }}</mat-label>\n <input matInput [ngModel]=\"doc().chartId || ''\" (ngModelChange)=\"setChartId($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.kind', 'Kind') }}</mat-label>\n <mat-select [ngModel]=\"doc().kind\" (ngModelChange)=\"setKind($event)\" [disabled]=\"isReadonly()\">\n @for (kind of chartKinds; track kind) {\n <mat-option [value]=\"kind\">\n {{ t('praxis.charts.editor.kind.' + kind, kind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.title', 'Title') }}</mat-label>\n <input matInput data-testid=\"chart-editor-title-input\" [ngModel]=\"titleValue()\" (ngModelChange)=\"setTitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.subtitle', 'Subtitle') }}</mat-label>\n <input matInput [ngModel]=\"subtitleValue()\" (ngModelChange)=\"setSubtitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sizingMode', 'Sizing') }}</mat-label>\n <mat-select [ngModel]=\"sizingModeValue()\" (ngModelChange)=\"setSizingMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of sizingModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.sizing.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n <mat-hint>{{ t('praxis.charts.editor.hint.sizingMode', 'Use fill-container only when the host widget provides a defined body height.') }}</mat-hint>\n </mat-form-field>\n\n @if (sizingModeValue() === 'fixed') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.height', 'Height') }}</mat-label>\n <input matInput [ngModel]=\"heightValue()\" (ngModelChange)=\"setSizingHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.height', 'Numbers are saved as pixels; CSS lengths such as 20rem are also accepted.') }}</mat-hint>\n </mat-form-field>\n }\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.minHeight', 'Minimum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMinHeightValue()\" (ngModelChange)=\"setSizingMinHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.minHeight', 'Set a readable minimum for compact dashboard widgets.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.maxHeight', 'Maximum height') }}</mat-label>\n <input matInput [ngModel]=\"sizingMaxHeightValue()\" (ngModelChange)=\"setSizingMaxHeight($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.maxHeight', 'Leave empty unless the chart must stop growing inside a flexible layout.') }}</mat-hint>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.aspectRatio', 'Aspect ratio') }}</mat-label>\n <input matInput [ngModel]=\"sizingAspectRatioValue()\" (ngModelChange)=\"setSizingAspectRatio($event)\" [disabled]=\"isReadonly()\" />\n <mat-hint>{{ t('praxis.charts.editor.hint.aspectRatio', 'Optional. Use values such as 1.777 or 16 / 9.') }}</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('data') {\n <div class=\"editor-stack\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sourceKind', 'Source') }}</mat-label>\n <mat-select [ngModel]=\"doc().source.kind\" (ngModelChange)=\"setSourceKind($event)\" [disabled]=\"isReadonly()\">\n @for (sourceKind of sourceKinds; track sourceKind) {\n <mat-option [value]=\"sourceKind\">\n {{ t('praxis.charts.editor.sourceKind.' + (sourceKind === 'praxis.stats' ? 'praxisStats' : 'derived'), sourceKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (doc().source.kind === 'praxis.stats') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.resource', 'Resource') }}</mat-label>\n @if (resourceOptions().length) {\n <mat-select [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\">\n @for (resource of resourceOptions(); track resource.id) {\n <mat-option [value]=\"resource.path\">{{ resource.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.operation', 'Operation') }}</mat-label>\n <mat-select\n [ngModel]=\"doc().source.operation || 'group-by'\"\n (ngModelChange)=\"setOperation($event)\"\n [disabled]=\"isReadonly()\"\n >\n @for (operation of operations; track operation) {\n <mat-option [value]=\"operation\">\n {{ t('praxis.charts.editor.operation.' + (operation === 'group-by' ? 'groupBy' : operation), operation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n @if (showTimeseriesControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.timeseriesTitle', 'Timeseries options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.granularity', 'Granularity') }}</mat-label>\n <mat-select [ngModel]=\"granularityValue()\" (ngModelChange)=\"setGranularity($event)\" [disabled]=\"isReadonly()\">\n @for (granularity of timeGranularities; track granularity) {\n <mat-option [value]=\"granularity\">\n {{ t('praxis.charts.editor.granularity.' + granularity, granularity) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-slide-toggle\n [ngModel]=\"fillGapsValue()\"\n (ngModelChange)=\"setFillGaps($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.fillGaps', 'Fill missing intervals') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showDistributionControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.distributionTitle', 'Distribution options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.distributionMode', 'Distribution mode') }}</mat-label>\n <mat-select [ngModel]=\"distributionModeValue()\" (ngModelChange)=\"setDistributionMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of distributionModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.distributionMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketSize', 'Bucket size') }}</mat-label>\n <input matInput [ngModel]=\"bucketSizeValue()\" (ngModelChange)=\"setBucketSize($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketCount', 'Bucket count') }}</mat-label>\n <input matInput [ngModel]=\"bucketCountValue()\" (ngModelChange)=\"setBucketCount($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n }\n </div>\n }\n\n @case ('motion') {\n <div class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"normalizedDocument().motion?.enabled !== false\"\n (ngModelChange)=\"setMotionEnabled($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.motionEnabled', 'Enable animations') }}\n </mat-slide-toggle>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.motionPreset', 'Motion preset') }}</mat-label>\n <mat-select\n [ngModel]=\"normalizedDocument().motion?.preset || 'standard'\"\n (ngModelChange)=\"setMotionPreset($event)\"\n [disabled]=\"isReadonly() || normalizedDocument().motion?.enabled === false\"\n >\n @for (preset of motionPresets; track preset) {\n <mat-option [value]=\"preset\">\n {{ t('praxis.charts.editor.motionPreset.' + preset, preset) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n @case ('appearance') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.featuresTitle', 'Display features') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('legend')\"\n (ngModelChange)=\"setFeatureEnabled('legend', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.legendEnabled', 'Show legend') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('labels')\"\n (ngModelChange)=\"setFeatureEnabled('labels', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.labelsEnabled', 'Show labels') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('tooltip')\"\n (ngModelChange)=\"setFeatureEnabled('tooltip', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.tooltipEnabled', 'Show tooltip') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.paletteTitle', 'Palette') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.themeVariant', 'Theme variant') }}</mat-label>\n <mat-select [ngModel]=\"themeVariantValue()\" (ngModelChange)=\"setThemeVariant($event)\" [disabled]=\"isReadonly()\">\n <mat-option [value]=\"''\">\n {{ t('praxis.charts.editor.themeVariant.none', 'No variant') }}\n </mat-option>\n @for (variant of themeVariants; track variant) {\n <mat-option [value]=\"variant\">\n {{ t('praxis.charts.editor.themeVariant.' + variant, variant) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteMode', 'Palette mode') }}</mat-label>\n <mat-select [ngModel]=\"paletteModeValue()\" (ngModelChange)=\"setPaletteMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of paletteModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.paletteMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (paletteModeValue() === 'token') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.paletteToken', 'Palette token') }}</mat-label>\n <mat-select [ngModel]=\"paletteTokenValue()\" (ngModelChange)=\"setPaletteToken($event)\" [disabled]=\"isReadonly()\">\n @for (token of paletteTokens; track token) {\n <mat-option [value]=\"token\">\n {{ t('praxis.charts.editor.paletteToken.' + token, token) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.palette', 'Palette colors') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"paletteValue()\"\n (ngModelChange)=\"setPalette($event)\"\n [disabled]=\"isReadonly()\"\n ></textarea>\n </mat-form-field>\n }\n\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.paletteHint', 'Use a registered token or comma-separated colors to persist theme.palette in the canonical contract.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.surfaceTitle', 'Surface') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.surfaceMode', 'Surface mode') }}</mat-label>\n <mat-select [ngModel]=\"surfaceModeValue()\" (ngModelChange)=\"setSurfaceMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of surfaceModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.surface.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.surfaceHint', 'Use embedded for dashboard widgets and contained only when the chart must own its visual surface.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.statesTitle', 'State messages') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyTitle', 'Empty title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('empty')\" (ngModelChange)=\"setStateTitle('empty', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyDescription', 'Empty description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('empty')\" (ngModelChange)=\"setStateDescription('empty', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingTitle', 'Loading title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('loading')\" (ngModelChange)=\"setStateTitle('loading', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingDescription', 'Loading description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('loading')\" (ngModelChange)=\"setStateDescription('loading', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorTitle', 'Error title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('error')\" (ngModelChange)=\"setStateTitle('error', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorDescription', 'Error description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('error')\" (ngModelChange)=\"setStateDescription('error', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('analytics') {\n <div class=\"editor-stack\">\n @if (showComboPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.comboTitle', 'Combo guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.comboHint', 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showPieDonutPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.pieDonutTitle', 'Composition guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.pieDonutHint', 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showScatterPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.scatterTitle', 'Scatter guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.scatterHint', 'Scatter charts use the first dimension as X and the first metric as Y, so keep both fields mapped.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.dimensionsTitle', 'Dimensions') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (dimension of dimensions(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimension', 'Dimension') }}</mat-label>\n @if (fieldOptions('dimension').length) {\n <mat-select [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('dimension'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimensionRole', 'Dimension role') }}</mat-label>\n <mat-select [ngModel]=\"dimension.role || 'category'\" (ngModelChange)=\"setDimensionRole($index, $event)\" [disabled]=\"isReadonly()\">\n @for (role of dimensionRoles; track role) {\n <mat-option [value]=\"role\">\n {{ t('praxis.charts.editor.dimensionRole.' + role, role) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeDimension($index)\" [disabled]=\"isReadonly() || dimensions().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeDimension', 'Remove dimension') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addDimension()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addDimension', 'Add dimension') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.metricsTitle', 'Metrics') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (metric of metrics(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metric', 'Metric') }}</mat-label>\n @if (fieldOptions('metric').length) {\n <mat-select [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('metric'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricLabel', 'Metric label') }}</mat-label>\n <input matInput [ngModel]=\"metric.label || ''\" (ngModelChange)=\"setMetricLabel($index, $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAggregation', 'Aggregation') }}</mat-label>\n <mat-select [ngModel]=\"metric.aggregation || 'sum'\" (ngModelChange)=\"setMetricAggregation($index, $event)\" [disabled]=\"isReadonly()\">\n @for (aggregation of metricAggregations; track aggregation) {\n <mat-option [value]=\"aggregation\">\n {{ t('praxis.charts.editor.metricAggregation.' + aggregation, aggregation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (showMetricAxisControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAxis', 'Axis') }}</mat-label>\n <mat-select [ngModel]=\"metric.axis || 'primary'\" (ngModelChange)=\"setMetricAxis($index, $event)\" [disabled]=\"isReadonly()\">\n @for (axis of metricAxes; track axis) {\n <mat-option [value]=\"axis\">\n {{ t('praxis.charts.editor.metricAxis.' + axis, axis) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (showMetricSeriesKindControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricSeriesKind', 'Series kind') }}</mat-label>\n <mat-select [ngModel]=\"metric.seriesKind || 'bar'\" (ngModelChange)=\"setMetricSeriesKind($index, $event)\" [disabled]=\"isReadonly()\">\n @for (seriesKind of metricSeriesKinds; track seriesKind) {\n <mat-option [value]=\"seriesKind\">\n {{ t('praxis.charts.editor.metricSeriesKind.' + seriesKind, seriesKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeMetric($index)\" [disabled]=\"isReadonly() || metrics().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeMetric', 'Remove metric') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addMetric()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addMetric', 'Add metric') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('events') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.pointClickTitle', 'Point click') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('pointClick')\" (ngModelChange)=\"setEventAction('pointClick', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('pointClick')\"\n (ngModelChange)=\"setEventMapping('pointClick', $event)\"\n [disabled]=\"isReadonly() || !eventAction('pointClick')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.selectionChangeTitle', 'Selection change') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('selectionChange')\" (ngModelChange)=\"setEventAction('selectionChange', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('selectionChange')\" (ngModelChange)=\"setEventTarget('selectionChange', $event)\" [disabled]=\"isReadonly() || !eventAction('selectionChange')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('selectionChange')\"\n (ngModelChange)=\"setEventMapping('selectionChange', $event)\"\n [disabled]=\"isReadonly() || !eventAction('selectionChange')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.drillDownTitle', 'Drill down') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('drillDown')\" (ngModelChange)=\"setEventAction('drillDown', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('drillDown')\"\n (ngModelChange)=\"setEventMapping('drillDown', $event)\"\n [disabled]=\"isReadonly() || !eventAction('drillDown')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.crossFilterTitle', 'Cross filter') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('crossFilter')\" (ngModelChange)=\"setEventAction('crossFilter', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('crossFilter')\" (ngModelChange)=\"setEventTarget('crossFilter', $event)\" [disabled]=\"isReadonly() || !eventAction('crossFilter')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('crossFilter')\"\n (ngModelChange)=\"setEventMapping('crossFilter', $event)\"\n [disabled]=\"isReadonly() || !eventAction('crossFilter')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('preview') {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n }\n }\n </mat-card-content>\n </mat-card>\n </div>\n\n <div class=\"editor-side\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.issues.title', 'Validation issues') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (issues().length) {\n <ul class=\"editor-issues\">\n @for (issue of issues(); track issueTrackBy($index, issue)) {\n <li class=\"editor-issue\">\n <strong>{{ issue.field }}</strong>\n <span>{{ issue.message }}</span>\n </li>\n }\n </ul>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.issues.empty', 'No issues were identified.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.preview.title', 'Chart preview') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (preview(); as chartPreview) {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n <praxis-chart [config]=\"chartPreview.config\" [data]=\"chartPreview.data\"></praxis-chart>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.preview.invalid', 'Preview is unavailable while the contract has blocking errors.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}.editor-shell{display:grid;gap:18px}.editor-nav{display:flex;gap:8px;flex-wrap:wrap}.editor-nav button.active{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 18%,transparent);color:var(--md-sys-color-primary, #1263b4)}.editor-layout{display:grid;gap:18px;grid-template-columns:minmax(0,1.35fr) minmax(320px,.9fr);align-items:start}.editor-form,.editor-side{display:grid;gap:16px}.editor-card{border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#1263b408,#1263b400)}.editor-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-stack{display:grid;gap:14px}.editor-row-card{padding:14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 54%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 92%,rgba(18,99,180,.04))}.editor-row-actions{display:flex;justify-content:flex-end}.editor-field{width:100%}.editor-issues{display:grid;gap:10px;margin:0;padding:0;list-style:none}.editor-issue{padding:12px 14px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-error, #b3261e) 8%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-error, #b3261e) 18%,transparent)}.editor-issue strong{display:block;margin-bottom:4px}.editor-caption{margin:0 0 12px;color:var(--md-sys-color-on-surface-variant, #5a5d67);font-size:.92rem}.editor-empty{padding:18px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-variant, #eceff4) 78%,transparent)}@media(max-width:960px){.editor-layout{grid-template-columns:minmax(0,1fr)}}\n"] }]
|
|
4109
4107
|
}], ctorParameters: () => [], propDecorators: { documentInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], modeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], availableResourcesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableResources", required: false }] }], availableFieldsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], availableTargetsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTargets", required: false }] }], apply: [{ type: i0.Output, args: ["apply"] }], save: [{ type: i0.Output, args: ["save"] }], resetChange: [{ type: i0.Output, args: ["resetChange"] }], documentChange: [{ type: i0.Output, args: ["documentChange"] }] } });
|
|
4110
4108
|
|
|
@@ -4160,8 +4158,8 @@ class PraxisChartWidgetConfigEditor {
|
|
|
4160
4158
|
},
|
|
4161
4159
|
};
|
|
4162
4160
|
}
|
|
4163
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4164
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
4161
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4162
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisChartWidgetConfigEditor, isStandalone: true, selector: "praxis-chart-widget-config-editor", inputs: { inputs: "inputs" }, viewQueries: [{ propertyName: "chartEditor", first: true, predicate: ["chartEditor"], descendants: true }], ngImport: i0, template: `
|
|
4165
4163
|
<section data-testid="chart-widget-config-editor">
|
|
4166
4164
|
<praxis-chart-config-editor
|
|
4167
4165
|
#chartEditor
|
|
@@ -4171,14 +4169,14 @@ class PraxisChartWidgetConfigEditor {
|
|
|
4171
4169
|
[availableTargets]="availableTargets"
|
|
4172
4170
|
/>
|
|
4173
4171
|
</section>
|
|
4174
|
-
`, isInline: true, dependencies: [{ kind: "
|
|
4172
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PraxisChartConfigEditor, selector: "praxis-chart-config-editor", inputs: ["document", "mode", "readonly", "availableResources", "availableFields", "availableTargets"], outputs: ["apply", "save", "resetChange", "documentChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4175
4173
|
}
|
|
4176
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartWidgetConfigEditor, decorators: [{
|
|
4177
4175
|
type: Component,
|
|
4178
4176
|
args: [{
|
|
4179
4177
|
selector: 'praxis-chart-widget-config-editor',
|
|
4180
4178
|
standalone: true,
|
|
4181
|
-
imports: [
|
|
4179
|
+
imports: [PraxisChartConfigEditor],
|
|
4182
4180
|
template: `
|
|
4183
4181
|
<section data-testid="chart-widget-config-editor">
|
|
4184
4182
|
<praxis-chart-config-editor
|
|
@@ -4557,12 +4555,12 @@ function providePraxisChartStateProbeMetadata() {
|
|
|
4557
4555
|
}
|
|
4558
4556
|
|
|
4559
4557
|
class PraxisChartShowcaseWidgetComponent {
|
|
4560
|
-
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
4561
|
-
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
4562
|
-
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : []));
|
|
4563
|
-
remoteDataResolver = input(null, ...(ngDevMode ? [{ debugName: "remoteDataResolver" }] : []));
|
|
4564
|
-
enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization" }] : []));
|
|
4565
|
-
viewMode = input('chart', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
|
|
4558
|
+
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
4559
|
+
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
4560
|
+
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : /* istanbul ignore next */ []));
|
|
4561
|
+
remoteDataResolver = input(null, ...(ngDevMode ? [{ debugName: "remoteDataResolver" }] : /* istanbul ignore next */ []));
|
|
4562
|
+
enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization" }] : /* istanbul ignore next */ []));
|
|
4563
|
+
viewMode = input('chart', ...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
4566
4564
|
pointClick = output();
|
|
4567
4565
|
selectionChange = output();
|
|
4568
4566
|
crossFilter = output();
|
|
@@ -4570,9 +4568,9 @@ class PraxisChartShowcaseWidgetComponent {
|
|
|
4570
4568
|
loadStateChange = output();
|
|
4571
4569
|
statsApi = inject(PraxisChartStatsApiService);
|
|
4572
4570
|
destroyRef = inject(DestroyRef);
|
|
4573
|
-
remoteResolvedRows = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedRows" }] : []));
|
|
4574
|
-
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
|
|
4575
|
-
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
|
|
4571
|
+
remoteResolvedRows = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedRows" }] : /* istanbul ignore next */ []));
|
|
4572
|
+
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : /* istanbul ignore next */ []));
|
|
4573
|
+
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : /* istanbul ignore next */ []));
|
|
4576
4574
|
previousRemoteSignature = null;
|
|
4577
4575
|
previousRemoteDataResolver = undefined;
|
|
4578
4576
|
resolvedRows = computed(() => {
|
|
@@ -4581,17 +4579,17 @@ class PraxisChartShowcaseWidgetComponent {
|
|
|
4581
4579
|
return explicitData;
|
|
4582
4580
|
}
|
|
4583
4581
|
return this.remoteResolvedRows() ?? [];
|
|
4584
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedRows" }] : []));
|
|
4585
|
-
tableId = computed(() => `${this.config().id || 'praxis-chart'}-showcase-table`, ...(ngDevMode ? [{ debugName: "tableId" }] : []));
|
|
4586
|
-
resolvedTitle = computed(() => resolveText(this.config().title) || 'Tabela analitica', ...(ngDevMode ? [{ debugName: "resolvedTitle" }] : []));
|
|
4587
|
-
resolvedSubtitle = computed(() => resolveText(this.config().subtitle) || 'Mesmo dataset em visualizacao tabular', ...(ngDevMode ? [{ debugName: "resolvedSubtitle" }] : []));
|
|
4588
|
-
loadingLabel = computed(() => resolveText(this.config().state?.loadingLabel) || 'Carregando analytics...', ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
|
|
4582
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedRows" }] : /* istanbul ignore next */ []));
|
|
4583
|
+
tableId = computed(() => `${this.config().id || 'praxis-chart'}-showcase-table`, ...(ngDevMode ? [{ debugName: "tableId" }] : /* istanbul ignore next */ []));
|
|
4584
|
+
resolvedTitle = computed(() => resolveText(this.config().title) || 'Tabela analitica', ...(ngDevMode ? [{ debugName: "resolvedTitle" }] : /* istanbul ignore next */ []));
|
|
4585
|
+
resolvedSubtitle = computed(() => resolveText(this.config().subtitle) || 'Mesmo dataset em visualizacao tabular', ...(ngDevMode ? [{ debugName: "resolvedSubtitle" }] : /* istanbul ignore next */ []));
|
|
4586
|
+
loadingLabel = computed(() => resolveText(this.config().state?.loadingLabel) || 'Carregando analytics...', ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
|
|
4589
4587
|
errorTitle = computed(() => this.remoteTechnicalError()
|
|
4590
4588
|
|| resolveText(this.config().state?.error?.title)
|
|
4591
|
-
|| 'Falha no contrato analitico', ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
|
|
4592
|
-
errorDescription = computed(() => resolveText(this.config().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
|
|
4593
|
-
stateMode = computed(() => resolveShowcaseStateMode(this.config(), this.remoteRuntimeState()), ...(ngDevMode ? [{ debugName: "stateMode" }] : []));
|
|
4594
|
-
tableConfig = computed(() => buildTableConfig(this.config(), this.resolvedRows()), ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
|
|
4589
|
+
|| 'Falha no contrato analitico', ...(ngDevMode ? [{ debugName: "errorTitle" }] : /* istanbul ignore next */ []));
|
|
4590
|
+
errorDescription = computed(() => resolveText(this.config().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : /* istanbul ignore next */ []));
|
|
4591
|
+
stateMode = computed(() => resolveShowcaseStateMode(this.config(), this.remoteRuntimeState()), ...(ngDevMode ? [{ debugName: "stateMode" }] : /* istanbul ignore next */ []));
|
|
4592
|
+
tableConfig = computed(() => buildTableConfig(this.config(), this.resolvedRows()), ...(ngDevMode ? [{ debugName: "tableConfig" }] : /* istanbul ignore next */ []));
|
|
4595
4593
|
constructor() {
|
|
4596
4594
|
effect(() => {
|
|
4597
4595
|
const config = this.config();
|
|
@@ -4679,8 +4677,8 @@ class PraxisChartShowcaseWidgetComponent {
|
|
|
4679
4677
|
data: row,
|
|
4680
4678
|
});
|
|
4681
4679
|
}
|
|
4682
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4683
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
4680
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4681
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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 }, chartDocument: { classPropertyName: "chartDocument", publicName: "chartDocument", isSignal: true, isRequired: false, transformFunction: null }, remoteDataResolver: { classPropertyName: "remoteDataResolver", publicName: "remoteDataResolver", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null }, viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", selectionChange: "selectionChange", crossFilter: "crossFilter", queryRequest: "queryRequest", loadStateChange: "loadStateChange" }, ngImport: i0, template: `
|
|
4684
4682
|
@if (viewMode() === 'chart') {
|
|
4685
4683
|
<praxis-chart
|
|
4686
4684
|
[config]="config()"
|
|
@@ -4716,11 +4714,11 @@ class PraxisChartShowcaseWidgetComponent {
|
|
|
4716
4714
|
(rowClick)="handleRowClick($event)"
|
|
4717
4715
|
></praxis-table>
|
|
4718
4716
|
}
|
|
4719
|
-
`, isInline: true, styles: [":host{display:block;height:100%;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: "
|
|
4717
|
+
`, isInline: true, styles: [":host{display:block;height:100%;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: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "queryContext", "remoteDataResolver", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "selectionChange", "crossFilter", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }, { kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "horizontalScroll", "enableCustomization", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4720
4718
|
}
|
|
4721
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4719
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, decorators: [{
|
|
4722
4720
|
type: Component,
|
|
4723
|
-
args: [{ selector: 'praxis-chart-showcase-widget', standalone: true, imports: [
|
|
4721
|
+
args: [{ selector: 'praxis-chart-showcase-widget', standalone: true, imports: [PraxisChartComponent, PraxisTable], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4724
4722
|
@if (viewMode() === 'chart') {
|
|
4725
4723
|
<praxis-chart
|
|
4726
4724
|
[config]="config()"
|
|
@@ -5002,10 +5000,10 @@ class PraxisChartMetadataRegistrationService {
|
|
|
5002
5000
|
this.registry.register(PRAXIS_CHART_SHOWCASE_WIDGET_METADATA);
|
|
5003
5001
|
this.registered = true;
|
|
5004
5002
|
}
|
|
5005
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
5006
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
5003
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartMetadataRegistrationService, deps: [{ token: i1$1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5004
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartMetadataRegistrationService, providedIn: 'root' });
|
|
5007
5005
|
}
|
|
5008
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5006
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartMetadataRegistrationService, decorators: [{
|
|
5009
5007
|
type: Injectable,
|
|
5010
5008
|
args: [{ providedIn: 'root' }]
|
|
5011
5009
|
}], ctorParameters: () => [{ type: i1$1.ComponentMetadataRegistry }] });
|
|
@@ -5116,10 +5114,10 @@ class PraxisChartSchemaMapperService {
|
|
|
5116
5114
|
}
|
|
5117
5115
|
return Object.keys(outputs).length ? outputs : undefined;
|
|
5118
5116
|
}
|
|
5119
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
5120
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
5117
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartSchemaMapperService, deps: [{ token: PraxisChartMetadataRegistrationService }, { token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5118
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartSchemaMapperService, providedIn: 'root' });
|
|
5121
5119
|
}
|
|
5122
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5120
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartSchemaMapperService, decorators: [{
|
|
5123
5121
|
type: Injectable,
|
|
5124
5122
|
args: [{ providedIn: 'root' }]
|
|
5125
5123
|
}], ctorParameters: () => [{ type: PraxisChartMetadataRegistrationService }, { type: PraxisChartCanonicalContractMapperService }] });
|
|
@@ -5167,10 +5165,10 @@ class PraxisChartBackendPayloadAdapterService {
|
|
|
5167
5165
|
toCanvasItem(payload, fallback = { col: 1, row: 1, colSpan: 6, rowSpan: 4 }) {
|
|
5168
5166
|
return payload.widget.canvasItem ?? fallback;
|
|
5169
5167
|
}
|
|
5170
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
5171
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
5168
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, deps: [{ token: PraxisChartSchemaMapperService }, { token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5169
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, providedIn: 'root' });
|
|
5172
5170
|
}
|
|
5173
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5171
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartBackendPayloadAdapterService, decorators: [{
|
|
5174
5172
|
type: Injectable,
|
|
5175
5173
|
args: [{ providedIn: 'root' }]
|
|
5176
5174
|
}], ctorParameters: () => [{ type: PraxisChartSchemaMapperService }, { type: PraxisChartCanonicalContractMapperService }] });
|
|
@@ -5308,10 +5306,10 @@ class AnalyticsChartConfigAdapterService {
|
|
|
5308
5306
|
...(projection.bindings.secondaryMetrics ?? []),
|
|
5309
5307
|
];
|
|
5310
5308
|
}
|
|
5311
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
5312
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
5309
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartConfigAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5310
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartConfigAdapterService, providedIn: 'root' });
|
|
5313
5311
|
}
|
|
5314
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5312
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartConfigAdapterService, decorators: [{
|
|
5315
5313
|
type: Injectable,
|
|
5316
5314
|
args: [{ providedIn: 'root' }]
|
|
5317
5315
|
}] });
|
|
@@ -5401,10 +5399,10 @@ class AnalyticsChartContractService {
|
|
|
5401
5399
|
subtitle: params.subtitle,
|
|
5402
5400
|
});
|
|
5403
5401
|
}
|
|
5404
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
5405
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
5402
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartContractService, deps: [{ token: i1$1.AnalyticsSchemaContractService }, { token: i1$1.AnalyticsPresentationResolver }, { token: AnalyticsChartConfigAdapterService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5403
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartContractService, providedIn: 'root' });
|
|
5406
5404
|
}
|
|
5407
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5405
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AnalyticsChartContractService, decorators: [{
|
|
5408
5406
|
type: Injectable,
|
|
5409
5407
|
args: [{ providedIn: 'root' }]
|
|
5410
5408
|
}], ctorParameters: () => [{ type: i1$1.AnalyticsSchemaContractService }, { type: i1$1.AnalyticsPresentationResolver }, { type: AnalyticsChartConfigAdapterService }] });
|
|
@@ -6196,17 +6194,17 @@ function buildShowcaseToggleActions() {
|
|
|
6196
6194
|
}
|
|
6197
6195
|
|
|
6198
6196
|
class PraxisChartCompositionShowcaseComponent {
|
|
6199
|
-
enableCustomization = input(false, ...(ngDevMode ?
|
|
6200
|
-
layoutMode = signal('widget', ...(ngDevMode ? [{ debugName: "layoutMode" }] : []));
|
|
6201
|
-
payloadMode = signal('group-by', ...(ngDevMode ? [{ debugName: "payloadMode" }] : []));
|
|
6202
|
-
scenarioMode = signal('baseline', ...(ngDevMode ? [{ debugName: "scenarioMode" }] : []));
|
|
6197
|
+
enableCustomization = input(false, { ...(ngDevMode ? { debugName: "enableCustomization" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6198
|
+
layoutMode = signal('widget', ...(ngDevMode ? [{ debugName: "layoutMode" }] : /* istanbul ignore next */ []));
|
|
6199
|
+
payloadMode = signal('group-by', ...(ngDevMode ? [{ debugName: "payloadMode" }] : /* istanbul ignore next */ []));
|
|
6200
|
+
scenarioMode = signal('baseline', ...(ngDevMode ? [{ debugName: "scenarioMode" }] : /* istanbul ignore next */ []));
|
|
6203
6201
|
backendPayloadAdapter = inject(PraxisChartBackendPayloadAdapterService);
|
|
6204
6202
|
runtimeContext = computed(() => ({
|
|
6205
6203
|
tenantId: 'demo-enterprise',
|
|
6206
6204
|
locale: 'pt-BR',
|
|
6207
6205
|
environment: 'published-api',
|
|
6208
6206
|
enableCustomization: this.enableCustomization(),
|
|
6209
|
-
}), ...(ngDevMode ? [{ debugName: "runtimeContext" }] : []));
|
|
6207
|
+
}), ...(ngDevMode ? [{ debugName: "runtimeContext" }] : /* istanbul ignore next */ []));
|
|
6210
6208
|
widgetPage = computed(() => {
|
|
6211
6209
|
switch (this.scenarioMode()) {
|
|
6212
6210
|
case 'interactive':
|
|
@@ -6257,7 +6255,7 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6257
6255
|
default:
|
|
6258
6256
|
return buildPraxisChartMockWidgetPage(this.backendPayloadAdapter);
|
|
6259
6257
|
}
|
|
6260
|
-
}, ...(ngDevMode ? [{ debugName: "widgetPage" }] : []));
|
|
6258
|
+
}, ...(ngDevMode ? [{ debugName: "widgetPage" }] : /* istanbul ignore next */ []));
|
|
6261
6259
|
canvasPage = computed(() => {
|
|
6262
6260
|
switch (this.scenarioMode()) {
|
|
6263
6261
|
case 'interactive':
|
|
@@ -6293,8 +6291,8 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6293
6291
|
default:
|
|
6294
6292
|
return buildPraxisChartMockCanvasPage(this.backendPayloadAdapter);
|
|
6295
6293
|
}
|
|
6296
|
-
}, ...(ngDevMode ? [{ debugName: "canvasPage" }] : []));
|
|
6297
|
-
resolvedPage = computed(() => (this.layoutMode() === 'canvas' ? this.canvasPage() : this.widgetPage()), ...(ngDevMode ? [{ debugName: "resolvedPage" }] : []));
|
|
6294
|
+
}, ...(ngDevMode ? [{ debugName: "canvasPage" }] : /* istanbul ignore next */ []));
|
|
6295
|
+
resolvedPage = computed(() => (this.layoutMode() === 'canvas' ? this.canvasPage() : this.widgetPage()), ...(ngDevMode ? [{ debugName: "resolvedPage" }] : /* istanbul ignore next */ []));
|
|
6298
6296
|
payloadTitle = computed(() => {
|
|
6299
6297
|
switch (this.payloadMode()) {
|
|
6300
6298
|
case 'timeseries':
|
|
@@ -6315,8 +6313,8 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6315
6313
|
default:
|
|
6316
6314
|
return 'Envelope group-by';
|
|
6317
6315
|
}
|
|
6318
|
-
}, ...(ngDevMode ? [{ debugName: "payloadTitle" }] : []));
|
|
6319
|
-
selectedPayloadJson = computed(() => JSON.stringify(this.selectedPayload(), null, 2), ...(ngDevMode ? [{ debugName: "selectedPayloadJson" }] : []));
|
|
6316
|
+
}, ...(ngDevMode ? [{ debugName: "payloadTitle" }] : /* istanbul ignore next */ []));
|
|
6317
|
+
selectedPayloadJson = computed(() => JSON.stringify(this.selectedPayload(), null, 2), ...(ngDevMode ? [{ debugName: "selectedPayloadJson" }] : /* istanbul ignore next */ []));
|
|
6320
6318
|
selectedPayload = computed(() => {
|
|
6321
6319
|
switch (this.payloadMode()) {
|
|
6322
6320
|
case 'timeseries':
|
|
@@ -6337,7 +6335,7 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6337
6335
|
default:
|
|
6338
6336
|
return PRAXIS_CHART_BACKEND_MOCK_BAR;
|
|
6339
6337
|
}
|
|
6340
|
-
}, ...(ngDevMode ? [{ debugName: "selectedPayload" }] : []));
|
|
6338
|
+
}, ...(ngDevMode ? [{ debugName: "selectedPayload" }] : /* istanbul ignore next */ []));
|
|
6341
6339
|
buildScenarioCanvasPage(primaryPayload) {
|
|
6342
6340
|
const base = buildPraxisChartMockCanvasPage(this.backendPayloadAdapter);
|
|
6343
6341
|
const widgets = [
|
|
@@ -6358,8 +6356,8 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6358
6356
|
},
|
|
6359
6357
|
};
|
|
6360
6358
|
}
|
|
6361
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6362
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "
|
|
6359
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartCompositionShowcaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6360
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: PraxisChartCompositionShowcaseComponent, isStandalone: true, selector: "praxis-chart-composition-showcase", inputs: { enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisCharts()], ngImport: i0, template: `
|
|
6363
6361
|
<section class="showcase-shell">
|
|
6364
6362
|
<header class="showcase-hero">
|
|
6365
6363
|
<div class="showcase-copy">
|
|
@@ -6471,11 +6469,11 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
6471
6469
|
</article>
|
|
6472
6470
|
</div>
|
|
6473
6471
|
</section>
|
|
6474
|
-
`, 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-caption{margin:6px 0 0;color:#516074;font-size:.88rem;line-height:1.45}.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: "
|
|
6472
|
+
`, 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-caption{margin:6px 0 0;color:#516074;font-size:.88rem;line-height:1.45}.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: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId", "showWidgetAssistantButton"], outputs: ["pageChange", "widgetEvent", "widgetSelectionChange", "widgetAssistantRequested", "widgetDiagnosticsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6475
6473
|
}
|
|
6476
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
6474
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartCompositionShowcaseComponent, decorators: [{
|
|
6477
6475
|
type: Component,
|
|
6478
|
-
args: [{ selector: 'praxis-chart-composition-showcase', standalone: true, imports: [
|
|
6476
|
+
args: [{ selector: 'praxis-chart-composition-showcase', standalone: true, imports: [DynamicWidgetPageComponent], providers: [providePraxisCharts()], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
6479
6477
|
<section class="showcase-shell">
|
|
6480
6478
|
<header class="showcase-hero">
|
|
6481
6479
|
<div class="showcase-copy">
|