@praxisui/charts 9.0.37 → 9.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -142,6 +142,18 @@ Use the component with either local data or governed remote execution:
142
142
 
143
143
  The public contract is `PraxisChartConfig` or the canonical `PraxisXUiChartContract`; raw ECharts options are adapter detail, not the host-facing model.
144
144
 
145
+ ## Accessibility
146
+
147
+ `PraxisChartComponent` materializes accessibility from the same canonical config and transformed rows used by the visual renderer:
148
+
149
+ - the default ECharts adapter enables its engine-owned ARIA description without exposing ECharts options through the public contract;
150
+ - every ready chart offers a localized `Show chart data` / `Exibir dados do gráfico` control with a structured HTML table;
151
+ - each table point is a native button, so Enter and Space follow browser keyboard conventions and activate the same renderer-neutral `pointClick` path as the visual point;
152
+ - when single selection is configured, the point button publishes `aria-pressed` and preserves the existing select/deselect and cross-filter payloads;
153
+ - loading, empty and error states remove the data controls, preventing stale chart values from remaining available to assistive technology.
154
+
155
+ The structured alternative is automatic. It does not add an accessibility field to `PraxisChartConfig` or `x-ui.chart`, and custom engines remain responsible for an accessible visual rendering while the component-level data alternative remains renderer-neutral.
156
+
145
157
  Supported canonical kinds are `bar`, `horizontal-bar`, `line`, `area`, `stacked-bar`,
146
158
  `stacked-area`, `combo`, `pie`, `donut`, `scatter`, `funnel`, `pyramid`, `treemap`, and `gauge`. Funnel,
147
159
  pyramid, and treemap require one categorical dimension and exactly one non-negative metric. Funnel
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-30T00:46:55.066Z",
3
+ "generatedAt": "2026-08-30T06:49:42.426Z",
4
4
  "packageName": "@praxisui/charts",
5
- "packageVersion": "9.0.37",
5
+ "packageVersion": "9.0.39",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 3,
@@ -1834,6 +1834,7 @@ const DEFAULT_CHART_CONFIG = {
1834
1834
  },
1835
1835
  series: [],
1836
1836
  };
1837
+ let chartAccessibilityPanelSequence = 0;
1837
1838
  class PraxisChartComponent {
1838
1839
  config = input(DEFAULT_CHART_CONFIG, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
1839
1840
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
@@ -1870,6 +1871,7 @@ class PraxisChartComponent {
1870
1871
  shellObserver = signal(null, ...(ngDevMode ? [{ debugName: "shellObserver" }] : /* istanbul ignore next */ []));
1871
1872
  themeObserver = signal(null, ...(ngDevMode ? [{ debugName: "themeObserver" }] : /* istanbul ignore next */ []));
1872
1873
  inheritedTheme = signal(null, ...(ngDevMode ? [{ debugName: "inheritedTheme" }] : /* istanbul ignore next */ []));
1874
+ inheritedHostTextColor = signal(undefined, ...(ngDevMode ? [{ debugName: "inheritedHostTextColor" }] : /* istanbul ignore next */ []));
1873
1875
  currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : /* istanbul ignore next */ []));
1874
1876
  remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : /* istanbul ignore next */ []));
1875
1877
  remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : /* istanbul ignore next */ []));
@@ -1878,6 +1880,9 @@ class PraxisChartComponent {
1878
1880
  mappedRuntimeConfig = signal(null, ...(ngDevMode ? [{ debugName: "mappedRuntimeConfig" }] : /* istanbul ignore next */ []));
1879
1881
  chartDocumentMappingError = signal(null, ...(ngDevMode ? [{ debugName: "chartDocumentMappingError" }] : /* istanbul ignore next */ []));
1880
1882
  activeSelectionSignature = signal(null, ...(ngDevMode ? [{ debugName: "activeSelectionSignature" }] : /* istanbul ignore next */ []));
1883
+ showAccessibleData = signal(false, ...(ngDevMode ? [{ debugName: "showAccessibleData" }] : /* istanbul ignore next */ []));
1884
+ accessibilityPanelId = `praxis-chart-data-${++chartAccessibilityPanelSequence}`;
1885
+ accessibilityPanelTitleId = `${this.accessibilityPanelId}-title`;
1881
1886
  fillContainerHeight = signal(false, ...(ngDevMode ? [{ debugName: "fillContainerHeight" }] : /* istanbul ignore next */ []));
1882
1887
  renderAttempt = signal(0, ...(ngDevMode ? [{ debugName: "renderAttempt" }] : /* istanbul ignore next */ []));
1883
1888
  resizeFrameId = null;
@@ -1892,7 +1897,7 @@ class PraxisChartComponent {
1892
1897
  remoteRequestSequence = 0;
1893
1898
  previousSelectionScopeSignature = null;
1894
1899
  effectiveConfig = computed(() => {
1895
- const base = this.applyInheritedTheme(this.mappedRuntimeConfig() ?? this.config(), this.inheritedTheme());
1900
+ const base = this.applyInheritedTheme(this.mappedRuntimeConfig() ?? this.config(), this.inheritedTheme(), this.inheritedHostTextColor());
1896
1901
  const runtimeQueryContext = normalizePraxisDataQueryContext(this.queryContext());
1897
1902
  const runtimeFilters = resolvePraxisFilterCriteria(this.filterCriteria(), runtimeQueryContext);
1898
1903
  if (!runtimeQueryContext
@@ -2001,6 +2006,7 @@ class PraxisChartComponent {
2001
2006
  }, ...(ngDevMode ? [{ debugName: "renderConfig" }] : /* istanbul ignore next */ []));
2002
2007
  loadingLabel = computed(() => this.i18n.resolve(this.renderConfig().state?.loadingLabel), ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
2003
2008
  transformedData = computed(() => this.transformer.transform(this.renderConfig(), this.resolvedData()), ...(ngDevMode ? [{ debugName: "transformedData" }] : /* istanbul ignore next */ []));
2009
+ accessiblePoints = computed(() => buildAccessibleChartPoints(this.renderConfig(), this.transformedData(), (value) => this.formatAccessibleValue(value), (index) => this.t('praxis.charts.runtime.accessibility.pointFallback', `Point ${index + 1}`, { index: index + 1 }), () => this.t('praxis.charts.runtime.accessibility.seriesFallback', 'Series')), ...(ngDevMode ? [{ debugName: "accessiblePoints" }] : /* istanbul ignore next */ []));
2004
2010
  dataDiagnostic = computed(() => {
2005
2011
  const config = this.renderConfig();
2006
2012
  const explicitData = this.data();
@@ -2026,6 +2032,16 @@ class PraxisChartComponent {
2026
2032
  : this.i18n.resolve(this.renderConfig().state?.error?.description)
2027
2033
  || this.t('praxis.charts.runtime.remoteErrorDescription', 'Revise a fonte de dados e os filtros antes de continuar.'), ...(ngDevMode ? [{ debugName: "errorDescription" }] : /* istanbul ignore next */ []));
2028
2034
  editChartLabel = computed(() => this.t('praxis.charts.runtime.editChart', 'Edit chart settings'), ...(ngDevMode ? [{ debugName: "editChartLabel" }] : /* istanbul ignore next */ []));
2035
+ accessibleDataToggleLabel = computed(() => this.showAccessibleData()
2036
+ ? this.t('praxis.charts.runtime.accessibility.hideData', 'Hide chart data')
2037
+ : this.t('praxis.charts.runtime.accessibility.showData', 'Show chart data'), ...(ngDevMode ? [{ debugName: "accessibleDataToggleLabel" }] : /* istanbul ignore next */ []));
2038
+ accessibleDataTitle = computed(() => this.i18n.resolve(this.renderConfig().title)
2039
+ || this.t('praxis.charts.runtime.accessibility.dataTitle', 'Chart data'), ...(ngDevMode ? [{ debugName: "accessibleDataTitle" }] : /* istanbul ignore next */ []));
2040
+ accessibleDataDescription = computed(() => this.i18n.resolve(this.renderConfig().subtitle)
2041
+ || this.t('praxis.charts.runtime.accessibility.dataDescription', 'Structured alternative for the values represented in the chart.'), ...(ngDevMode ? [{ debugName: "accessibleDataDescription" }] : /* istanbul ignore next */ []));
2042
+ accessibleCategoryHeading = computed(() => this.t('praxis.charts.runtime.accessibility.category', 'Category'), ...(ngDevMode ? [{ debugName: "accessibleCategoryHeading" }] : /* istanbul ignore next */ []));
2043
+ accessibleSeriesHeading = computed(() => this.t('praxis.charts.runtime.accessibility.series', 'Series'), ...(ngDevMode ? [{ debugName: "accessibleSeriesHeading" }] : /* istanbul ignore next */ []));
2044
+ accessibleValueHeading = computed(() => this.t('praxis.charts.runtime.accessibility.value', 'Value'), ...(ngDevMode ? [{ debugName: "accessibleValueHeading" }] : /* istanbul ignore next */ []));
2029
2045
  loadState = computed(() => {
2030
2046
  if (this.chartDocumentMappingError()) {
2031
2047
  return 'error';
@@ -2113,6 +2129,9 @@ class PraxisChartComponent {
2113
2129
  });
2114
2130
  effect(() => {
2115
2131
  const nextState = this.loadState();
2132
+ if (nextState !== 'ready' || !this.accessiblePoints().length) {
2133
+ this.showAccessibleData.set(false);
2134
+ }
2116
2135
  if (this.currentLoadState() !== nextState) {
2117
2136
  this.currentLoadState.set(nextState);
2118
2137
  this.loadStateChange.emit(nextState);
@@ -2232,8 +2251,23 @@ class PraxisChartComponent {
2232
2251
  const description = descriptions[diagnostic.code];
2233
2252
  return this.t(description.key, description.text);
2234
2253
  }
2235
- t(key, fallback) {
2236
- return this.i18n.t(key, undefined, fallback, PRAXIS_CHARTS_I18N_NAMESPACE);
2254
+ formatAccessibleValue(value) {
2255
+ if (Array.isArray(value)) {
2256
+ return value.map((item) => this.formatAccessibleValue(item)).join(', ');
2257
+ }
2258
+ if (value instanceof Date) {
2259
+ return this.i18n.formatDate(value);
2260
+ }
2261
+ if (typeof value === 'number') {
2262
+ return this.i18n.formatNumber(value);
2263
+ }
2264
+ if (value === null || value === undefined || value === '') {
2265
+ return this.t('praxis.charts.runtime.accessibility.notAvailable', 'Not available');
2266
+ }
2267
+ return String(value);
2268
+ }
2269
+ t(key, fallback, params) {
2270
+ return this.i18n.t(key, params, fallback, PRAXIS_CHARTS_I18N_NAMESPACE);
2237
2271
  }
2238
2272
  canOpenConfigEditor() {
2239
2273
  return this.enableCustomization() && !!this.settingsPanel && !!this.runtimeChartDocument();
@@ -2381,6 +2415,34 @@ class PraxisChartComponent {
2381
2415
  });
2382
2416
  }
2383
2417
  }
2418
+ toggleAccessibleData() {
2419
+ this.showAccessibleData.update((visible) => !visible);
2420
+ }
2421
+ activateAccessiblePoint(point) {
2422
+ this.handlePointClick({
2423
+ chartId: point.chartId,
2424
+ seriesId: point.seriesId,
2425
+ seriesName: point.seriesName,
2426
+ category: point.category,
2427
+ value: point.value,
2428
+ data: point.data,
2429
+ });
2430
+ }
2431
+ accessiblePointPressed(point) {
2432
+ if (!this.renderConfig().interactions?.selection) {
2433
+ return null;
2434
+ }
2435
+ return this.activeSelectionSignature() === this.buildPointSelectionSignature(this.renderConfig(), point)
2436
+ ? 'true'
2437
+ : 'false';
2438
+ }
2439
+ accessiblePointActionLabel(point) {
2440
+ return this.t('praxis.charts.runtime.accessibility.activatePoint', 'Activate {{ category }}, {{ series }}: {{ value }}', {
2441
+ category: point.categoryLabel,
2442
+ series: point.seriesLabel,
2443
+ value: point.valueLabel,
2444
+ });
2445
+ }
2384
2446
  buildSelectionEvent(config, event) {
2385
2447
  const action = config.interactions?.eventActions?.selectionChange;
2386
2448
  const filters = this.buildEventFilters(config, event, action?.mapping);
@@ -2594,6 +2656,7 @@ class PraxisChartComponent {
2594
2656
  ? this.cloneTheme(PRAXIS_CHART_THEME_VARIANTS[chartThemePresetId])
2595
2657
  : {};
2596
2658
  const hostTextColor = getComputedStyle(host).color.trim();
2659
+ this.inheritedHostTextColor.set(hostTextColor || undefined);
2597
2660
  const theme = {
2598
2661
  ...preset,
2599
2662
  textColor: preset.textColor || hostTextColor || undefined,
@@ -2611,17 +2674,22 @@ class PraxisChartComponent {
2611
2674
  isThemeVariant(value) {
2612
2675
  return Object.prototype.hasOwnProperty.call(PRAXIS_CHART_THEME_VARIANTS, value);
2613
2676
  }
2614
- applyInheritedTheme(config, inherited) {
2677
+ applyInheritedTheme(config, inherited, hostTextColor) {
2615
2678
  if (!inherited) {
2616
2679
  return config;
2617
2680
  }
2618
2681
  const explicit = config.theme;
2682
+ const explicitSurface = explicit?.surface;
2683
+ const usesHostSurface = explicitSurface?.mode === 'embedded'
2684
+ || explicitSurface?.background?.trim().toLowerCase() === 'transparent';
2619
2685
  return {
2620
2686
  ...config,
2621
2687
  theme: {
2622
2688
  palette: explicit?.palette ?? inherited.palette,
2623
- backgroundColor: explicit?.backgroundColor ?? inherited.backgroundColor,
2624
- textColor: explicit?.textColor ?? inherited.textColor,
2689
+ backgroundColor: explicit?.backgroundColor
2690
+ ?? (usesHostSurface ? undefined : inherited.backgroundColor),
2691
+ textColor: explicit?.textColor
2692
+ ?? (usesHostSurface ? hostTextColor : inherited.textColor),
2625
2693
  borderRadius: explicit?.borderRadius ?? inherited.borderRadius,
2626
2694
  legend: this.mergeOptionalThemeObject(inherited.legend, explicit?.legend),
2627
2695
  tooltip: this.mergeOptionalThemeObject(inherited.tooltip, explicit?.tooltip),
@@ -2744,6 +2812,22 @@ class PraxisChartComponent {
2744
2812
  ></button>
2745
2813
  }
2746
2814
 
2815
+ @if (loadState() === 'ready' && accessiblePoints().length) {
2816
+ <button
2817
+ class="praxis-chart-data-trigger"
2818
+ [class.praxis-chart-data-trigger-with-settings]="canOpenConfigEditor()"
2819
+ praxisIconButton="table_view"
2820
+ size="compact"
2821
+ appearance="filled"
2822
+ type="button"
2823
+ [attr.aria-label]="accessibleDataToggleLabel()"
2824
+ [attr.aria-controls]="accessibilityPanelId"
2825
+ [attr.aria-expanded]="showAccessibleData()"
2826
+ [matTooltip]="accessibleDataToggleLabel()"
2827
+ (click)="toggleAccessibleData()"
2828
+ ></button>
2829
+ }
2830
+
2747
2831
  @if (loadState() === 'loading') {
2748
2832
  <div class="praxis-chart-state praxis-chart-state-loading">
2749
2833
  <div class="praxis-chart-loading-hero" aria-hidden="true">
@@ -2787,10 +2871,53 @@ class PraxisChartComponent {
2787
2871
  }
2788
2872
  </div>
2789
2873
  } @else {
2790
- <div #chartHost class="praxis-chart-host"></div>
2874
+ @if (showAccessibleData()) {
2875
+ <section
2876
+ class="praxis-chart-accessible-data"
2877
+ [id]="accessibilityPanelId"
2878
+ role="region"
2879
+ [attr.aria-labelledby]="accessibilityPanelTitleId"
2880
+ >
2881
+ <h3 [id]="accessibilityPanelTitleId">{{ accessibleDataTitle() }}</h3>
2882
+ <table>
2883
+ <caption>{{ accessibleDataDescription() }}</caption>
2884
+ <thead>
2885
+ <tr>
2886
+ <th scope="col">{{ accessibleCategoryHeading() }}</th>
2887
+ <th scope="col">{{ accessibleSeriesHeading() }}</th>
2888
+ <th scope="col">{{ accessibleValueHeading() }}</th>
2889
+ </tr>
2890
+ </thead>
2891
+ <tbody>
2892
+ @for (point of accessiblePoints(); track point.id) {
2893
+ <tr>
2894
+ <th scope="row">
2895
+ <button
2896
+ type="button"
2897
+ class="praxis-chart-point-action"
2898
+ [attr.aria-label]="accessiblePointActionLabel(point)"
2899
+ [attr.aria-pressed]="accessiblePointPressed(point)"
2900
+ (click)="activateAccessiblePoint(point)"
2901
+ >
2902
+ {{ point.categoryLabel }}
2903
+ </button>
2904
+ </th>
2905
+ <td>{{ point.seriesLabel }}</td>
2906
+ <td>{{ point.valueLabel }}</td>
2907
+ </tr>
2908
+ }
2909
+ </tbody>
2910
+ </table>
2911
+ </section>
2912
+ }
2913
+ <div
2914
+ #chartHost
2915
+ class="praxis-chart-host"
2916
+ [attr.aria-hidden]="showAccessibleData() ? 'true' : null"
2917
+ ></div>
2791
2918
  }
2792
2919
  </section>
2793
- `, isInline: true, styles: [":host{display:block;min-height:var(--praxis-chart-runtime-height, 320px);min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}:host(.praxis-chart-host-fill-container){height:100%;min-height: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;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%;min-height:inherit}.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(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20))}.praxis-chart-state-description{font-size:.925rem;color:color-mix(in srgb,var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface-variant, #5a5d67)) 72%,transparent);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: MatTooltipModule }, { kind: "directive", type: i1$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2920
+ `, isInline: true, styles: [":host{display:block;min-height:var(--praxis-chart-runtime-height, 320px);min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}:host(.praxis-chart-host-fill-container){height:100%;min-height: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;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-data-trigger{position:absolute;top:10px;right:10px;z-index:5;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-data-trigger-with-settings{right:50px}.praxis-chart-accessible-data{position:absolute;inset:0;z-index:4;overflow:auto;padding:52px 16px 16px;color:var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20));background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-surface-bg, var(--md-sys-color-surface-container-lowest, #fff)) )}.praxis-chart-accessible-data h3{margin:0 0 12px;font-size:1rem;line-height:1.4}.praxis-chart-accessible-data table{width:100%;border-collapse:collapse;font-size:.875rem}.praxis-chart-accessible-data caption{padding:0 0 12px;text-align:left;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.praxis-chart-accessible-data th,.praxis-chart-accessible-data td{padding:8px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 44%,transparent);text-align:left}.praxis-chart-accessible-data thead th{font-weight:600}.praxis-chart-point-action{appearance:none;padding:4px 6px;border:0;border-radius:4px;color:var(--md-sys-color-primary, #1263b4);background:transparent;font:inherit;font-weight:600;text-align:left;cursor:pointer}.praxis-chart-point-action:hover{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 10%,transparent)}.praxis-chart-point-action:focus-visible{outline:3px solid var(--md-sys-color-primary, #1263b4);outline-offset:2px}.praxis-chart-point-action[aria-pressed=true]{color:var(--md-sys-color-on-primary-container, #001b3f);background:var(--md-sys-color-primary-container, #d7e3ff)}.praxis-chart-host{width:100%;height:100%;min-height:inherit}.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(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20))}.praxis-chart-state-description{font-size:.925rem;color:color-mix(in srgb,var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface-variant, #5a5d67)) 72%,transparent);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: MatTooltipModule }, { kind: "directive", type: i1$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2794
2921
  }
2795
2922
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisChartComponent, decorators: [{
2796
2923
  type: Component,
@@ -2825,6 +2952,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
2825
2952
  ></button>
2826
2953
  }
2827
2954
 
2955
+ @if (loadState() === 'ready' && accessiblePoints().length) {
2956
+ <button
2957
+ class="praxis-chart-data-trigger"
2958
+ [class.praxis-chart-data-trigger-with-settings]="canOpenConfigEditor()"
2959
+ praxisIconButton="table_view"
2960
+ size="compact"
2961
+ appearance="filled"
2962
+ type="button"
2963
+ [attr.aria-label]="accessibleDataToggleLabel()"
2964
+ [attr.aria-controls]="accessibilityPanelId"
2965
+ [attr.aria-expanded]="showAccessibleData()"
2966
+ [matTooltip]="accessibleDataToggleLabel()"
2967
+ (click)="toggleAccessibleData()"
2968
+ ></button>
2969
+ }
2970
+
2828
2971
  @if (loadState() === 'loading') {
2829
2972
  <div class="praxis-chart-state praxis-chart-state-loading">
2830
2973
  <div class="praxis-chart-loading-hero" aria-hidden="true">
@@ -2868,10 +3011,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
2868
3011
  }
2869
3012
  </div>
2870
3013
  } @else {
2871
- <div #chartHost class="praxis-chart-host"></div>
3014
+ @if (showAccessibleData()) {
3015
+ <section
3016
+ class="praxis-chart-accessible-data"
3017
+ [id]="accessibilityPanelId"
3018
+ role="region"
3019
+ [attr.aria-labelledby]="accessibilityPanelTitleId"
3020
+ >
3021
+ <h3 [id]="accessibilityPanelTitleId">{{ accessibleDataTitle() }}</h3>
3022
+ <table>
3023
+ <caption>{{ accessibleDataDescription() }}</caption>
3024
+ <thead>
3025
+ <tr>
3026
+ <th scope="col">{{ accessibleCategoryHeading() }}</th>
3027
+ <th scope="col">{{ accessibleSeriesHeading() }}</th>
3028
+ <th scope="col">{{ accessibleValueHeading() }}</th>
3029
+ </tr>
3030
+ </thead>
3031
+ <tbody>
3032
+ @for (point of accessiblePoints(); track point.id) {
3033
+ <tr>
3034
+ <th scope="row">
3035
+ <button
3036
+ type="button"
3037
+ class="praxis-chart-point-action"
3038
+ [attr.aria-label]="accessiblePointActionLabel(point)"
3039
+ [attr.aria-pressed]="accessiblePointPressed(point)"
3040
+ (click)="activateAccessiblePoint(point)"
3041
+ >
3042
+ {{ point.categoryLabel }}
3043
+ </button>
3044
+ </th>
3045
+ <td>{{ point.seriesLabel }}</td>
3046
+ <td>{{ point.valueLabel }}</td>
3047
+ </tr>
3048
+ }
3049
+ </tbody>
3050
+ </table>
3051
+ </section>
3052
+ }
3053
+ <div
3054
+ #chartHost
3055
+ class="praxis-chart-host"
3056
+ [attr.aria-hidden]="showAccessibleData() ? 'true' : null"
3057
+ ></div>
2872
3058
  }
2873
3059
  </section>
2874
- `, styles: [":host{display:block;min-height:var(--praxis-chart-runtime-height, 320px);min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}:host(.praxis-chart-host-fill-container){height:100%;min-height: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;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%;min-height:inherit}.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(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20))}.praxis-chart-state-description{font-size:.925rem;color:color-mix(in srgb,var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface-variant, #5a5d67)) 72%,transparent);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"] }]
3060
+ `, styles: [":host{display:block;min-height:var(--praxis-chart-runtime-height, 320px);min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}:host(.praxis-chart-host-fill-container){height:100%;min-height: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;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-data-trigger{position:absolute;top:10px;right:10px;z-index:5;--praxis-icon-button-filled-background: color-mix(in srgb, var(--md-sys-color-surface, #fff) 88%, rgba(18, 99, 180, .12));--praxis-icon-button-filled-foreground: var(--md-sys-color-on-surface-variant, #44474f);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-data-trigger-with-settings{right:50px}.praxis-chart-accessible-data{position:absolute;inset:0;z-index:4;overflow:auto;padding:52px 16px 16px;color:var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20));background:var( --praxis-chart-config-surface-bg, var(--praxis-chart-surface-bg, var(--md-sys-color-surface-container-lowest, #fff)) )}.praxis-chart-accessible-data h3{margin:0 0 12px;font-size:1rem;line-height:1.4}.praxis-chart-accessible-data table{width:100%;border-collapse:collapse;font-size:.875rem}.praxis-chart-accessible-data caption{padding:0 0 12px;text-align:left;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.praxis-chart-accessible-data th,.praxis-chart-accessible-data td{padding:8px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 44%,transparent);text-align:left}.praxis-chart-accessible-data thead th{font-weight:600}.praxis-chart-point-action{appearance:none;padding:4px 6px;border:0;border-radius:4px;color:var(--md-sys-color-primary, #1263b4);background:transparent;font:inherit;font-weight:600;text-align:left;cursor:pointer}.praxis-chart-point-action:hover{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 10%,transparent)}.praxis-chart-point-action:focus-visible{outline:3px solid var(--md-sys-color-primary, #1263b4);outline-offset:2px}.praxis-chart-point-action[aria-pressed=true]{color:var(--md-sys-color-on-primary-container, #001b3f);background:var(--md-sys-color-primary-container, #d7e3ff)}.praxis-chart-host{width:100%;height:100%;min-height:inherit}.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(--praxis-chart-config-text-color, var(--md-sys-color-on-surface, #1a1b20))}.praxis-chart-state-description{font-size:.925rem;color:color-mix(in srgb,var(--praxis-chart-config-text-color, var(--md-sys-color-on-surface-variant, #5a5d67)) 72%,transparent);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"] }]
2875
3061
  }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], chartDocument: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartDocument", required: false }] }], filterCriteria: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterCriteria", required: false }] }], queryContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryContext", required: false }] }], remoteDataResolver: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteDataResolver", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], availableResources: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableResources", required: false }] }], availableFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], availableTargets: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTargets", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], pointAction: [{ type: i0.Output, args: ["pointAction"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], drillDown: [{ type: i0.Output, args: ["drillDown"] }], crossFilter: [{ type: i0.Output, args: ["crossFilter"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }], chartDocumentApplied: [{ type: i0.Output, args: ["chartDocumentApplied"] }], chartDocumentSaved: [{ type: i0.Output, args: ["chartDocumentSaved"] }], chartHost: [{ type: i0.ViewChild, args: ['chartHost', { isSignal: true }] }] } });
2876
3062
  function mergeRemoteQueryContext(config, queryContext, runtimeFilters) {
2877
3063
  const dataSource = config.dataSource;
@@ -2916,6 +3102,111 @@ function resolveUnsupportedStatsFilterExpressionDiagnostic(queryContext) {
2916
3102
  : '';
2917
3103
  return `Praxis Charts received a governed queryContext.filterExpression, but praxis.stats execution currently accepts only flat statsRequest.filter criteria. The expression was rejected instead of being flattened into an unsafe AND filter.${decision}${reason}`;
2918
3104
  }
3105
+ function buildAccessibleChartPoints(config, transformed, formatValue, pointFallback, seriesFallback) {
3106
+ if (transformed.mode === 'gauge') {
3107
+ const gauge = transformed.gauge;
3108
+ if (!gauge) {
3109
+ return [];
3110
+ }
3111
+ const series = config.series[0];
3112
+ return [createAccessiblePoint({
3113
+ id: gauge.id,
3114
+ config,
3115
+ seriesId: series?.id,
3116
+ seriesName: series?.name ?? series?.metric?.label,
3117
+ category: gauge.name,
3118
+ value: gauge.value,
3119
+ data: gauge.data,
3120
+ index: 0,
3121
+ formatValue,
3122
+ pointFallback,
3123
+ seriesFallback,
3124
+ })];
3125
+ }
3126
+ if (transformed.mode === 'pie' || transformed.mode === 'funnel' || transformed.mode === 'treemap') {
3127
+ const series = config.series[0];
3128
+ return transformed.slices.map((slice, index) => createAccessiblePoint({
3129
+ id: slice.id,
3130
+ config,
3131
+ seriesId: series?.id,
3132
+ seriesName: series?.name ?? series?.metric?.label,
3133
+ category: slice.name,
3134
+ value: slice.value,
3135
+ data: slice.data,
3136
+ index,
3137
+ formatValue,
3138
+ pointFallback,
3139
+ seriesFallback,
3140
+ }));
3141
+ }
3142
+ return transformed.series.flatMap((series) => series.points.flatMap((point, index) => {
3143
+ const value = accessiblePointValue(point);
3144
+ if (value === null || value === undefined) {
3145
+ return [];
3146
+ }
3147
+ const data = accessiblePointData(point, transformed.categories[index], value);
3148
+ const category = transformed.categories[index]
3149
+ ?? accessibleCategoryFromData(config, data)
3150
+ ?? pointFallback(index);
3151
+ return [createAccessiblePoint({
3152
+ id: `${series.id}:${index}:${category}`,
3153
+ config,
3154
+ seriesId: series.id,
3155
+ seriesName: series.name,
3156
+ category,
3157
+ value,
3158
+ data,
3159
+ index,
3160
+ formatValue,
3161
+ pointFallback,
3162
+ seriesFallback,
3163
+ })];
3164
+ }));
3165
+ }
3166
+ function createAccessiblePoint(options) {
3167
+ const categoryLabel = options.category?.trim() || options.pointFallback(options.index);
3168
+ const seriesLabel = options.seriesName?.trim()
3169
+ || options.seriesId?.trim()
3170
+ || options.seriesFallback();
3171
+ return {
3172
+ id: options.id,
3173
+ chartId: options.config.id,
3174
+ seriesId: options.seriesId,
3175
+ seriesName: options.seriesName,
3176
+ category: options.category,
3177
+ value: options.value,
3178
+ data: options.data ?? {
3179
+ category: options.category,
3180
+ value: options.value,
3181
+ },
3182
+ categoryLabel,
3183
+ seriesLabel,
3184
+ valueLabel: options.formatValue(options.value),
3185
+ };
3186
+ }
3187
+ function accessiblePointValue(point) {
3188
+ if (Array.isArray(point)) {
3189
+ return point.length > 1 ? point[1] : point[0];
3190
+ }
3191
+ if (point && typeof point === 'object' && 'value' in point) {
3192
+ return accessiblePointValue(point['value']);
3193
+ }
3194
+ return point;
3195
+ }
3196
+ function accessiblePointData(point, category, value) {
3197
+ if (point && typeof point === 'object' && !Array.isArray(point)) {
3198
+ return point;
3199
+ }
3200
+ return { category, value };
3201
+ }
3202
+ function accessibleCategoryFromData(config, data) {
3203
+ const categoryField = config.axes?.x?.field
3204
+ ?? config.series.find((series) => series.categoryField)?.categoryField;
3205
+ const value = categoryField ? data[categoryField] : data['category'];
3206
+ return value === null || value === undefined || value === ''
3207
+ ? undefined
3208
+ : String(value);
3209
+ }
2919
3210
  function normalizeRemoteDataResolverResult$1(result) {
2920
3211
  if (Array.isArray(result)) {
2921
3212
  return of(result);
@@ -4420,11 +4711,13 @@ const CARTESIAN_GRID_BOTTOM_WITHOUT_LEGEND = 40;
4420
4711
  const DEFAULT_VALUE_LOCALE = 'pt-BR';
4421
4712
  class EChartsOptionBuilderService {
4422
4713
  transformer;
4714
+ i18n = inject(PraxisI18nService);
4423
4715
  constructor(transformer) {
4424
4716
  this.transformer = transformer;
4425
4717
  }
4426
4718
  build(config, rows) {
4427
4719
  const transformed = this.transformer.transform(config, rows);
4720
+ const accessibility = this.buildAccessibility(config, transformed);
4428
4721
  const palette = config.theme?.palette?.length ? config.theme.palette : PRAXIS_CHART_DEFAULT_PALETTE;
4429
4722
  const tooltipEnabled = config.theme?.tooltip?.enabled ?? true;
4430
4723
  const legendVisible = config.theme?.legend?.visible ?? true;
@@ -4494,6 +4787,7 @@ class EChartsOptionBuilderService {
4494
4787
  }],
4495
4788
  };
4496
4789
  return {
4790
+ aria: accessibility,
4497
4791
  backgroundColor: config.theme?.backgroundColor,
4498
4792
  color: palette,
4499
4793
  title: this.buildTitle(config, 'center', textColor),
@@ -4538,6 +4832,7 @@ class EChartsOptionBuilderService {
4538
4832
  })),
4539
4833
  };
4540
4834
  return {
4835
+ aria: accessibility,
4541
4836
  backgroundColor: config.theme?.backgroundColor,
4542
4837
  color: palette,
4543
4838
  title: this.buildTitle(config, 'center', textColor),
@@ -4588,6 +4883,7 @@ class EChartsOptionBuilderService {
4588
4883
  })),
4589
4884
  };
4590
4885
  return {
4886
+ aria: accessibility,
4591
4887
  backgroundColor: config.theme?.backgroundColor,
4592
4888
  color: palette,
4593
4889
  title: this.buildTitle(config, 'center', textColor),
@@ -4631,6 +4927,7 @@ class EChartsOptionBuilderService {
4631
4927
  })),
4632
4928
  };
4633
4929
  return {
4930
+ aria: accessibility,
4634
4931
  backgroundColor: config.theme?.backgroundColor,
4635
4932
  color: palette,
4636
4933
  title: this.buildTitle(config, 'center', textColor),
@@ -4660,6 +4957,7 @@ class EChartsOptionBuilderService {
4660
4957
  data: series.points,
4661
4958
  }));
4662
4959
  return {
4960
+ aria: accessibility,
4663
4961
  backgroundColor: config.theme?.backgroundColor,
4664
4962
  color: palette,
4665
4963
  title: this.buildTitle(config, 'left', textColor),
@@ -4706,6 +5004,7 @@ class EChartsOptionBuilderService {
4706
5004
  ? 'time'
4707
5005
  : config.axes?.x?.type ?? 'category';
4708
5006
  return {
5007
+ aria: accessibility,
4709
5008
  backgroundColor: config.theme?.backgroundColor,
4710
5009
  color: palette,
4711
5010
  title: this.buildTitle(config, 'left', textColor),
@@ -4792,6 +5091,57 @@ class EChartsOptionBuilderService {
4792
5091
  }
4793
5092
  return undefined;
4794
5093
  }
5094
+ buildAccessibility(config, transformed) {
5095
+ const title = this.i18n.resolve(config.title).trim();
5096
+ const seriesCount = transformed.mode === 'gauge'
5097
+ ? transformed.gauge ? 1 : 0
5098
+ : transformed.slices.length
5099
+ ? 1
5100
+ : transformed.series.length;
5101
+ const pointCount = transformed.mode === 'gauge'
5102
+ ? transformed.gauge ? 1 : 0
5103
+ : transformed.slices.length
5104
+ ? transformed.slices.length
5105
+ : transformed.series.reduce((total, series) => total + series.points.length, 0);
5106
+ const params = {
5107
+ title,
5108
+ type: this.chartTypeLabel(config.type),
5109
+ seriesCount,
5110
+ pointCount,
5111
+ };
5112
+ const description = title
5113
+ ? this.t('praxis.charts.runtime.accessibility.ariaDescriptionWithTitle', 'Chart "{{ title }}". Type: {{ type }}. Series: {{ seriesCount }}. Points: {{ pointCount }}. Complete values are available under "Show chart data".', params)
5114
+ : this.t('praxis.charts.runtime.accessibility.ariaDescriptionWithoutTitle', 'Chart. Type: {{ type }}. Series: {{ seriesCount }}. Points: {{ pointCount }}. Complete values are available under "Show chart data".', params);
5115
+ return {
5116
+ enabled: true,
5117
+ label: {
5118
+ enabled: true,
5119
+ description,
5120
+ },
5121
+ };
5122
+ }
5123
+ chartTypeLabel(type) {
5124
+ const fallbacks = {
5125
+ bar: 'Bar',
5126
+ 'horizontal-bar': 'Horizontal bar',
5127
+ line: 'Line',
5128
+ area: 'Area',
5129
+ 'stacked-bar': 'Stacked bar',
5130
+ 'stacked-area': 'Stacked area',
5131
+ combo: 'Combination',
5132
+ pie: 'Pie',
5133
+ donut: 'Donut',
5134
+ scatter: 'Scatter',
5135
+ funnel: 'Funnel',
5136
+ pyramid: 'Pyramid',
5137
+ treemap: 'Treemap',
5138
+ gauge: 'Gauge',
5139
+ };
5140
+ return this.t(`praxis.charts.runtime.accessibility.chartType.${type}`, fallbacks[type]);
5141
+ }
5142
+ t(key, fallback, params) {
5143
+ return this.i18n.t(key, params, fallback, PRAXIS_CHARTS_I18N_NAMESPACE);
5144
+ }
4795
5145
  resolveTextColor(config) {
4796
5146
  return config.theme?.textColor ?? '#4b5563';
4797
5147
  }
@@ -5263,17 +5613,19 @@ class EChartsEngineAdapter {
5263
5613
  });
5264
5614
  const zrender = chart?.getZr?.();
5265
5615
  this.zrenderClickHandler = (event) => {
5616
+ const occurredAt = Date.now();
5266
5617
  window.setTimeout(() => {
5267
- if (Date.now() - this.lastSeriesClickAt < 80) {
5618
+ if (this.isSeriesFallbackDuplicate(occurredAt)) {
5268
5619
  return;
5269
5620
  }
5270
- this.emitCategoryClickFromGrid(event, payload);
5621
+ this.emitCategoryClickFromGrid(event, payload, occurredAt);
5271
5622
  }, 0);
5272
5623
  };
5273
5624
  zrender?.on?.('click', this.zrenderClickHandler);
5274
5625
  this.domClickHandler = (event) => {
5626
+ const occurredAt = Date.now();
5275
5627
  window.setTimeout(() => {
5276
- if (Date.now() - this.lastSeriesClickAt < 80) {
5628
+ if (this.isSeriesFallbackDuplicate(occurredAt)) {
5277
5629
  return;
5278
5630
  }
5279
5631
  const rect = host.getBoundingClientRect();
@@ -5282,7 +5634,7 @@ class EChartsEngineAdapter {
5282
5634
  this.emitCategoryClickFromGrid({
5283
5635
  offsetX,
5284
5636
  offsetY,
5285
- }, payload);
5637
+ }, payload, occurredAt);
5286
5638
  }, 0);
5287
5639
  };
5288
5640
  host.addEventListener('click', this.domClickHandler, true);
@@ -5338,7 +5690,7 @@ class EChartsEngineAdapter {
5338
5690
  ? undefined
5339
5691
  : String(candidate);
5340
5692
  }
5341
- emitCategoryClickFromGrid(event, payload) {
5693
+ emitCategoryClickFromGrid(event, payload, occurredAt) {
5342
5694
  const chart = this.chart;
5343
5695
  if (!chart || typeof chart.containPixel !== 'function' || typeof chart.convertFromPixel !== 'function') {
5344
5696
  return;
@@ -5373,7 +5725,7 @@ class EChartsEngineAdapter {
5373
5725
  horizontal,
5374
5726
  categoryIndex,
5375
5727
  });
5376
- if (this.isDuplicateGridClick(signature)) {
5728
+ if (this.isDuplicateGridClick(signature, occurredAt)) {
5377
5729
  return;
5378
5730
  }
5379
5731
  const usesCanonicalStatsRows = payload.config.dataSource?.kind === 'remote'
@@ -5399,11 +5751,14 @@ class EChartsEngineAdapter {
5399
5751
  data,
5400
5752
  });
5401
5753
  }
5402
- isDuplicateGridClick(signature) {
5403
- const now = Date.now();
5754
+ isSeriesFallbackDuplicate(occurredAt) {
5755
+ return this.lastSeriesClickAt > 0
5756
+ && Math.abs(occurredAt - this.lastSeriesClickAt) < 80;
5757
+ }
5758
+ isDuplicateGridClick(signature, occurredAt) {
5404
5759
  const duplicate = this.lastGridClick?.signature === signature
5405
- && now - this.lastGridClick.at < 80;
5406
- this.lastGridClick = { signature, at: now };
5760
+ && Math.abs(occurredAt - this.lastGridClick.at) < 80;
5761
+ this.lastGridClick = { signature, at: occurredAt };
5407
5762
  return duplicate;
5408
5763
  }
5409
5764
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EChartsEngineAdapter, deps: [{ token: EChartsOptionBuilderService }], target: i0.ɵɵFactoryTarget.Injectable });
@@ -5446,6 +5801,33 @@ const PRAXIS_CHARTS_EN_US = {
5446
5801
  'praxis.charts.runtime.gaugeValueInvalid': 'The gauge requires one finite metric value.',
5447
5802
  'praxis.charts.runtime.gaugeValueOutOfRange': 'The gauge value is outside the configured scale. Review the metric and scale instead of clamping the value.',
5448
5803
  'praxis.charts.runtime.booleanFalseLabel': 'Not {{ label }}',
5804
+ 'praxis.charts.runtime.accessibility.showData': 'Show chart data',
5805
+ 'praxis.charts.runtime.accessibility.hideData': 'Hide chart data',
5806
+ 'praxis.charts.runtime.accessibility.dataTitle': 'Chart data',
5807
+ 'praxis.charts.runtime.accessibility.dataDescription': 'Structured alternative for the values represented in the chart.',
5808
+ 'praxis.charts.runtime.accessibility.category': 'Category',
5809
+ 'praxis.charts.runtime.accessibility.series': 'Series',
5810
+ 'praxis.charts.runtime.accessibility.value': 'Value',
5811
+ 'praxis.charts.runtime.accessibility.pointFallback': 'Point {{ index }}',
5812
+ 'praxis.charts.runtime.accessibility.seriesFallback': 'Series',
5813
+ 'praxis.charts.runtime.accessibility.notAvailable': 'Not available',
5814
+ 'praxis.charts.runtime.accessibility.activatePoint': 'Activate {{ category }}, {{ series }}: {{ value }}',
5815
+ 'praxis.charts.runtime.accessibility.ariaDescriptionWithTitle': 'Chart "{{ title }}". Type: {{ type }}. Series: {{ seriesCount }}. Points: {{ pointCount }}. Complete values are available under "Show chart data".',
5816
+ 'praxis.charts.runtime.accessibility.ariaDescriptionWithoutTitle': 'Chart. Type: {{ type }}. Series: {{ seriesCount }}. Points: {{ pointCount }}. Complete values are available under "Show chart data".',
5817
+ 'praxis.charts.runtime.accessibility.chartType.bar': 'Bar',
5818
+ 'praxis.charts.runtime.accessibility.chartType.horizontal-bar': 'Horizontal bar',
5819
+ 'praxis.charts.runtime.accessibility.chartType.line': 'Line',
5820
+ 'praxis.charts.runtime.accessibility.chartType.area': 'Area',
5821
+ 'praxis.charts.runtime.accessibility.chartType.stacked-bar': 'Stacked bar',
5822
+ 'praxis.charts.runtime.accessibility.chartType.stacked-area': 'Stacked area',
5823
+ 'praxis.charts.runtime.accessibility.chartType.combo': 'Combination',
5824
+ 'praxis.charts.runtime.accessibility.chartType.pie': 'Pie',
5825
+ 'praxis.charts.runtime.accessibility.chartType.donut': 'Donut',
5826
+ 'praxis.charts.runtime.accessibility.chartType.scatter': 'Scatter',
5827
+ 'praxis.charts.runtime.accessibility.chartType.funnel': 'Funnel',
5828
+ 'praxis.charts.runtime.accessibility.chartType.pyramid': 'Pyramid',
5829
+ 'praxis.charts.runtime.accessibility.chartType.treemap': 'Treemap',
5830
+ 'praxis.charts.runtime.accessibility.chartType.gauge': 'Gauge',
5449
5831
  'praxis.charts.runtime.comparisonCurrent': 'Current',
5450
5832
  'praxis.charts.runtime.comparisonPrevious': 'Previous',
5451
5833
  'praxis.charts.widget.missingDocument': 'This widget does not have a canonical chart document yet. Runtime inputs are preserved until a canonical chartDocument is provided by the host.',
@@ -5681,6 +6063,33 @@ const PRAXIS_CHARTS_PT_BR = {
5681
6063
  'praxis.charts.runtime.gaugeValueInvalid': 'O velocímetro exige um único valor numérico finito.',
5682
6064
  'praxis.charts.runtime.gaugeValueOutOfRange': 'O valor do velocímetro está fora da escala configurada. Revise a métrica e a escala em vez de limitar o valor silenciosamente.',
5683
6065
  'praxis.charts.runtime.booleanFalseLabel': 'Não {{ label }}',
6066
+ 'praxis.charts.runtime.accessibility.showData': 'Exibir dados do gráfico',
6067
+ 'praxis.charts.runtime.accessibility.hideData': 'Ocultar dados do gráfico',
6068
+ 'praxis.charts.runtime.accessibility.dataTitle': 'Dados do gráfico',
6069
+ 'praxis.charts.runtime.accessibility.dataDescription': 'Alternativa estruturada para os valores representados no gráfico.',
6070
+ 'praxis.charts.runtime.accessibility.category': 'Categoria',
6071
+ 'praxis.charts.runtime.accessibility.series': 'Série',
6072
+ 'praxis.charts.runtime.accessibility.value': 'Valor',
6073
+ 'praxis.charts.runtime.accessibility.pointFallback': 'Ponto {{ index }}',
6074
+ 'praxis.charts.runtime.accessibility.seriesFallback': 'Série',
6075
+ 'praxis.charts.runtime.accessibility.notAvailable': 'Não disponível',
6076
+ 'praxis.charts.runtime.accessibility.activatePoint': 'Ativar {{ category }}, {{ series }}: {{ value }}',
6077
+ 'praxis.charts.runtime.accessibility.ariaDescriptionWithTitle': 'Gráfico "{{ title }}". Tipo: {{ type }}. Séries: {{ seriesCount }}. Pontos: {{ pointCount }}. Os valores completos estão disponíveis em "Exibir dados do gráfico".',
6078
+ 'praxis.charts.runtime.accessibility.ariaDescriptionWithoutTitle': 'Gráfico. Tipo: {{ type }}. Séries: {{ seriesCount }}. Pontos: {{ pointCount }}. Os valores completos estão disponíveis em "Exibir dados do gráfico".',
6079
+ 'praxis.charts.runtime.accessibility.chartType.bar': 'Barras',
6080
+ 'praxis.charts.runtime.accessibility.chartType.horizontal-bar': 'Barras horizontais',
6081
+ 'praxis.charts.runtime.accessibility.chartType.line': 'Linhas',
6082
+ 'praxis.charts.runtime.accessibility.chartType.area': 'Área',
6083
+ 'praxis.charts.runtime.accessibility.chartType.stacked-bar': 'Barras empilhadas',
6084
+ 'praxis.charts.runtime.accessibility.chartType.stacked-area': 'Área empilhada',
6085
+ 'praxis.charts.runtime.accessibility.chartType.combo': 'Combinado',
6086
+ 'praxis.charts.runtime.accessibility.chartType.pie': 'Pizza',
6087
+ 'praxis.charts.runtime.accessibility.chartType.donut': 'Rosca',
6088
+ 'praxis.charts.runtime.accessibility.chartType.scatter': 'Dispersão',
6089
+ 'praxis.charts.runtime.accessibility.chartType.funnel': 'Funil',
6090
+ 'praxis.charts.runtime.accessibility.chartType.pyramid': 'Pirâmide',
6091
+ 'praxis.charts.runtime.accessibility.chartType.treemap': 'Mapa de árvore',
6092
+ 'praxis.charts.runtime.accessibility.chartType.gauge': 'Velocímetro',
5684
6093
  'praxis.charts.runtime.comparisonCurrent': 'Atual',
5685
6094
  'praxis.charts.runtime.comparisonPrevious': 'Anterior',
5686
6095
  'praxis.charts.widget.missingDocument': 'Este widget ainda não possui um documento canônico de gráfico. Os inputs runtime são preservados até o host fornecer um chartDocument canônico.',
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@praxisui/charts",
3
- "version": "9.0.37",
3
+ "version": "9.0.39",
4
4
  "description": "Metadata-driven charts library for Praxis UI Angular with engine adapters and Apache ECharts as the initial renderer.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/core": "^9.0.37",
8
+ "@praxisui/core": "^9.0.39",
9
9
  "@angular/forms": "^21.0.0",
10
10
  "@angular/material": "^21.0.0",
11
- "@praxisui/table": "^9.0.37",
11
+ "@praxisui/table": "^9.0.39",
12
12
  "rxjs": "~7.8.0"
13
13
  },
14
14
  "dependencies": {
@@ -547,6 +547,12 @@ interface ChartEditorTargetOption {
547
547
  events?: ChartEditorEventKey[];
548
548
  }
549
549
 
550
+ interface PraxisChartAccessiblePoint extends PraxisChartPointEvent {
551
+ id: string;
552
+ categoryLabel: string;
553
+ seriesLabel: string;
554
+ valueLabel: string;
555
+ }
550
556
  declare class PraxisChartComponent {
551
557
  readonly config: _angular_core.InputSignal<PraxisChartConfig>;
552
558
  readonly data: _angular_core.InputSignal<PraxisChartDataRow[] | null>;
@@ -583,6 +589,7 @@ declare class PraxisChartComponent {
583
589
  private readonly shellObserver;
584
590
  private readonly themeObserver;
585
591
  private readonly inheritedTheme;
592
+ private readonly inheritedHostTextColor;
586
593
  private readonly currentLoadState;
587
594
  private readonly remoteResolvedData;
588
595
  private readonly remoteRuntimeState;
@@ -591,6 +598,9 @@ declare class PraxisChartComponent {
591
598
  private readonly mappedRuntimeConfig;
592
599
  private readonly chartDocumentMappingError;
593
600
  private readonly activeSelectionSignature;
601
+ readonly showAccessibleData: _angular_core.WritableSignal<boolean>;
602
+ readonly accessibilityPanelId: string;
603
+ readonly accessibilityPanelTitleId: string;
594
604
  private readonly fillContainerHeight;
595
605
  private readonly renderAttempt;
596
606
  private resizeFrameId;
@@ -619,16 +629,24 @@ declare class PraxisChartComponent {
619
629
  readonly surfaceTextColor: _angular_core.Signal<string | null>;
620
630
  readonly renderConfig: _angular_core.Signal<PraxisChartConfig>;
621
631
  readonly loadingLabel: _angular_core.Signal<string>;
622
- readonly transformedData: _angular_core.Signal<_praxisui_charts.PraxisChartTransformedData>;
632
+ readonly transformedData: _angular_core.Signal<PraxisChartTransformedData>;
633
+ readonly accessiblePoints: _angular_core.Signal<PraxisChartAccessiblePoint[]>;
623
634
  readonly dataDiagnostic: _angular_core.Signal<PraxisChartDataDiagnostic | null>;
624
635
  readonly emptyTitle: _angular_core.Signal<string>;
625
636
  readonly emptyDescription: _angular_core.Signal<string>;
626
637
  readonly errorTitle: _angular_core.Signal<string>;
627
638
  readonly errorDescription: _angular_core.Signal<string>;
628
639
  readonly editChartLabel: _angular_core.Signal<string>;
640
+ readonly accessibleDataToggleLabel: _angular_core.Signal<string>;
641
+ readonly accessibleDataTitle: _angular_core.Signal<string>;
642
+ readonly accessibleDataDescription: _angular_core.Signal<string>;
643
+ readonly accessibleCategoryHeading: _angular_core.Signal<string>;
644
+ readonly accessibleSeriesHeading: _angular_core.Signal<string>;
645
+ readonly accessibleValueHeading: _angular_core.Signal<string>;
629
646
  readonly loadState: _angular_core.Signal<PraxisChartLoadState>;
630
647
  constructor();
631
648
  private resolveDataDiagnosticDescription;
649
+ private formatAccessibleValue;
632
650
  private t;
633
651
  canOpenConfigEditor(): boolean;
634
652
  openConfigEditor(): Promise<void>;
@@ -637,6 +655,10 @@ declare class PraxisChartComponent {
637
655
  private isConfigEditorContextResult;
638
656
  private asRecord;
639
657
  private handlePointClick;
658
+ toggleAccessibleData(): void;
659
+ activateAccessiblePoint(point: PraxisChartAccessiblePoint): void;
660
+ accessiblePointPressed(point: PraxisChartAccessiblePoint): string | null;
661
+ accessiblePointActionLabel(point: PraxisChartAccessiblePoint): string;
640
662
  private buildSelectionEvent;
641
663
  private buildPointSelectionSignature;
642
664
  private buildSelectionScopeSignature;