diff --git a/playground/playground.tsx b/playground/playground.tsx index c163367dd2..b897b0fa20 100644 --- a/playground/playground.tsx +++ b/playground/playground.tsx @@ -19,13 +19,14 @@ import React from 'react'; -import { Chart, AreaSeries, LineSeries, BarSeries, ScaleType } from '../src'; +import { Chart, AreaSeries, LineSeries, BarSeries, ScaleType, Settings } from '../src'; export class Playground extends React.Component { render() { return (
+ { initialized, chartContainerDimensions: { width, height }, forwardStageRef, + a11ySettings, } = this.props; if (!initialized || width === 0 || height === 0) { return null; } return ( - +
+ + + +
); } @@ -157,6 +171,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { left: 0, top: 0, }, + a11ySettings: DEFAULT_A11Y_SETTINGS, }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -167,6 +182,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { initialized: true, geometries: geometries(state), chartContainerDimensions: state.parentDimensions, + a11ySettings: getA11ySettingsSelector(state), }; }; diff --git a/src/chart_types/goal_chart/state/chart_state.tsx b/src/chart_types/goal_chart/state/chart_state.tsx index 6447440040..90077346b6 100644 --- a/src/chart_types/goal_chart/state/chart_state.tsx +++ b/src/chart_types/goal_chart/state/chart_state.tsx @@ -29,6 +29,7 @@ import { LegendItemLabel } from '../../../state/selectors/get_legend_items_label import { DebugState } from '../../../state/types'; import { Dimensions } from '../../../utils/dimensions'; import { Goal } from '../renderer/canvas/connected_component'; +import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description'; import { getSpecOrNull } from './selectors/goal_spec'; import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible'; import { createOnElementClickCaller } from './selectors/on_element_click_caller'; @@ -120,6 +121,10 @@ export class GoalState implements InternalChartState { this.onElementClickCaller(globalState); } + getChartTypeDescription(globalState: GlobalChartState) { + return getChartTypeDescriptionSelector(globalState); + } + // TODO getProjectionContainerArea(): Dimensions { return { width: 0, height: 0, top: 0, left: 0 }; diff --git a/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts b/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts new file mode 100644 index 0000000000..d8416442e2 --- /dev/null +++ b/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getSpecOrNull } from './goal_spec'; + +/** @internal */ +export const getChartTypeDescriptionSelector = createCachedSelector([getSpecOrNull], (spec) => { + return `${spec?.subtype ?? 'goal'} chart`; +})(getChartIdSelector); diff --git a/src/chart_types/heatmap/renderer/canvas/connected_component.tsx b/src/chart_types/heatmap/renderer/canvas/connected_component.tsx index a3f0d639ed..a1a573bc76 100644 --- a/src/chart_types/heatmap/renderer/canvas/connected_component.tsx +++ b/src/chart_types/heatmap/renderer/canvas/connected_component.tsx @@ -21,8 +21,14 @@ import React, { RefObject } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; +import { ScreenReaderSummary } from '../../../../components/accessibility'; import { onChartRendered } from '../../../../state/actions/chart'; import { GlobalChartState } from '../../../../state/chart_state'; +import { + A11ySettings, + DEFAULT_A11Y_SETTINGS, + getA11ySettingsSelector, +} from '../../../../state/selectors/get_accessibility_config'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; import { Dimensions } from '../../../../utils/dimensions'; import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; @@ -34,6 +40,7 @@ interface ReactiveChartStateProps { initialized: boolean; geometries: ShapeViewModel; chartContainerDimensions: Dimensions; + a11ySettings: A11ySettings; } interface ReactiveChartDispatchProps { @@ -97,26 +104,34 @@ class Component extends React.Component { } } + // eslint-disable-next-line @typescript-eslint/member-ordering render() { const { initialized, chartContainerDimensions: { width, height }, forwardStageRef, + a11ySettings, } = this.props; if (!initialized || width === 0 || height === 0) { return null; } return ( - +
+ + + +
); } } @@ -138,6 +153,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { left: 0, top: 0, }, + a11ySettings: DEFAULT_A11Y_SETTINGS, }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -148,6 +164,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { initialized: true, geometries: geometries(state), chartContainerDimensions: getHeatmapContainerSizeSelector(state), + a11ySettings: getA11ySettingsSelector(state), }; }; diff --git a/src/chart_types/heatmap/state/chart_state.tsx b/src/chart_types/heatmap/state/chart_state.tsx index 4bfbab888d..63a82a58f1 100644 --- a/src/chart_types/heatmap/state/chart_state.tsx +++ b/src/chart_types/heatmap/state/chart_state.tsx @@ -130,6 +130,10 @@ export class HeatmapState implements InternalChartState { return getDebugStateSelector(globalState); } + getChartTypeDescription() { + return 'Heatmap chart'; + } + eventCallbacks(globalState: GlobalChartState) { this.onElementOverCaller(globalState); this.onElementOutCaller(globalState); diff --git a/src/chart_types/partition_chart/renderer/canvas/partition.tsx b/src/chart_types/partition_chart/renderer/canvas/partition.tsx index f993240c3b..0477f1cc72 100644 --- a/src/chart_types/partition_chart/renderer/canvas/partition.tsx +++ b/src/chart_types/partition_chart/renderer/canvas/partition.tsx @@ -21,9 +21,15 @@ import React, { MouseEvent, RefObject } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; +import { ScreenReaderSummary } from '../../../../components/accessibility'; import { clearCanvas } from '../../../../renderers/canvas'; import { onChartRendered } from '../../../../state/actions/chart'; import { ChartId, GlobalChartState } from '../../../../state/chart_state'; +import { + A11ySettings, + DEFAULT_A11Y_SETTINGS, + getA11ySettingsSelector, +} from '../../../../state/selectors/get_accessibility_config'; import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; @@ -59,6 +65,7 @@ interface ReactiveChartStateProps { multiGeometries: ShapeViewModel[]; chartContainerDimensions: Dimensions; chartId: ChartId; + a11ySettings: A11ySettings; } interface ReactiveChartDispatchProps { @@ -141,23 +148,29 @@ class PartitionComponent extends React.Component { forwardStageRef, initialized, chartContainerDimensions: { width, height }, + a11ySettings, } = this.props; if (!initialized || width === 0 || height === 0) { return null; } - return ( - +
+ + + +
); } @@ -205,6 +218,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { left: 0, top: 0, }, + a11ySettings: DEFAULT_A11Y_SETTINGS, }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -219,6 +233,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { chartContainerDimensions: getChartContainerDimensionsSelector(state), geometriesFoci: partitionDrilldownFocus(state), chartId: getChartIdSelector(state), + a11ySettings: getA11ySettingsSelector(state), }; }; diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx index b9a39b0578..d7625e17bd 100644 --- a/src/chart_types/partition_chart/state/chart_state.tsx +++ b/src/chart_types/partition_chart/state/chart_state.tsx @@ -27,6 +27,7 @@ import { DebugState } from '../../../state/types'; import { Dimensions } from '../../../utils/dimensions'; import { render } from '../renderer/dom/layered_partition_chart'; import { computeLegendSelector } from './selectors/compute_legend'; +import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description'; import { getDebugStateSelector } from './selectors/get_debug_state'; import { getLegendItemsExtra } from './selectors/get_legend_items_extra'; import { getLegendItemsLabels } from './selectors/get_legend_items_labels'; @@ -134,4 +135,8 @@ export class PartitionState implements InternalChartState { getDebugState(state: GlobalChartState): DebugState { return getDebugStateSelector(state); } + + getChartTypeDescription(state: GlobalChartState): string { + return getChartTypeDescriptionSelector(state); + } } diff --git a/src/chart_types/partition_chart/state/selectors/get_chart_type_description.ts b/src/chart_types/partition_chart/state/selectors/get_chart_type_description.ts new file mode 100644 index 0000000000..f2823f16af --- /dev/null +++ b/src/chart_types/partition_chart/state/selectors/get_chart_type_description.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import createCachedSelector from 're-reselect'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getPartitionSpec } from './partition_spec'; + +/** @internal */ +export const getChartTypeDescriptionSelector = createCachedSelector([getPartitionSpec], (partitionSpec): string => { + return `${partitionSpec?.config.partitionLayout} chart` ?? 'Partition chart'; +})(getChartIdSelector); diff --git a/src/chart_types/wordcloud/renderer/svg/connected_component.tsx b/src/chart_types/wordcloud/renderer/svg/connected_component.tsx index 8f5a883389..46719d580c 100644 --- a/src/chart_types/wordcloud/renderer/svg/connected_component.tsx +++ b/src/chart_types/wordcloud/renderer/svg/connected_component.tsx @@ -23,8 +23,14 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; +import { ScreenReaderSummary } from '../../../../components/accessibility'; import { onChartRendered } from '../../../../state/actions/chart'; import { GlobalChartState } from '../../../../state/chart_state'; +import { + A11ySettings, + DEFAULT_A11Y_SETTINGS, + getA11ySettingsSelector, +} from '../../../../state/selectors/get_accessibility_config'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; import { Dimensions } from '../../../../utils/dimensions'; import { Configs, Datum, nullShapeViewModel, ShapeViewModel, Word } from '../../layout/types/viewmodel_types'; @@ -120,7 +126,7 @@ function layoutMaker(config: Configs, data: Datum[]) { } const View = ({ words, conf }: { words: Word[]; conf: Configs }) => ( - + {words.map((d, i) => { return ( @@ -148,6 +154,7 @@ interface ReactiveChartStateProps { initialized: boolean; geometries: ShapeViewModel; chartContainerDimensions: Dimensions; + a11ySettings: A11ySettings; } interface ReactiveChartDispatchProps { @@ -176,6 +183,7 @@ class Component extends React.Component { initialized, chartContainerDimensions: { width, height }, geometries: { wordcloudViewModel }, + a11ySettings, } = this.props; if (!initialized || width === 0 || height === 0) { return null; @@ -214,7 +222,12 @@ class Component extends React.Component { ); } - return ; + return ( +
+ + +
+ ); } } @@ -235,6 +248,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { left: 0, top: 0, }, + a11ySettings: DEFAULT_A11Y_SETTINGS, }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -245,6 +259,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { initialized: true, geometries: geometries(state), chartContainerDimensions: state.parentDimensions, + a11ySettings: getA11ySettingsSelector(state), }; }; diff --git a/src/chart_types/wordcloud/state/chart_state.tsx b/src/chart_types/wordcloud/state/chart_state.tsx index 4e8298fcbd..34a6767ba4 100644 --- a/src/chart_types/wordcloud/state/chart_state.tsx +++ b/src/chart_types/wordcloud/state/chart_state.tsx @@ -113,6 +113,10 @@ export class WordcloudState implements InternalChartState { this.onElementClickCaller(globalState); } + getChartTypeDescription() { + return 'Word cloud chart'; + } + // TODO getProjectionContainerArea(): Dimensions { return { width: 0, height: 0, top: 0, left: 0 }; diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx index ee1aba5f03..a1bfbb2dcc 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -22,13 +22,12 @@ import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { LegendItem } from '../../../../common/legend'; -import { Description } from '../../../../components/accessibility/description'; -import { Label } from '../../../../components/accessibility/label'; +import { ScreenReaderSummary } from '../../../../components/accessibility'; import { onChartRendered } from '../../../../state/actions/chart'; import { GlobalChartState } from '../../../../state/chart_state'; import { A11ySettings, - DEFAULT_A11_SETTINGS, + DEFAULT_A11Y_SETTINGS, getA11ySettingsSelector, } from '../../../../state/selectors/get_accessibility_config'; import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions'; @@ -55,13 +54,12 @@ import { import { computeSeriesGeometriesSelector } from '../../state/selectors/compute_series_geometries'; import { getAxesStylesSelector } from '../../state/selectors/get_axis_styles'; import { getHighlightedSeriesSelector } from '../../state/selectors/get_highlighted_series'; -import { getSeriesTypes } from '../../state/selectors/get_series_types'; import { getAnnotationSpecsSelector, getAxisSpecsSelector } from '../../state/selectors/get_specs'; import { isChartEmptySelector } from '../../state/selectors/is_chart_empty'; import { Geometries, Transform } from '../../state/utils/types'; import { LinesGrid } from '../../utils/grid_lines'; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map'; -import { AxisSpec, AnnotationSpec, SeriesType } from '../../utils/specs'; +import { AxisSpec, AnnotationSpec } from '../../utils/specs'; import { renderXYChartCanvas2d } from './renderers'; /** @internal */ @@ -84,7 +82,6 @@ export interface ReactiveChartStateProps { annotationDimensions: Map; annotationSpecs: AnnotationSpec[]; panelGeoms: PanelGeoms; - seriesTypes: Set; a11ySettings: A11ySettings; } @@ -162,7 +159,6 @@ class XYChartComponent extends React.Component { initialized, isChartEmpty, chartContainerDimensions: { width, height }, - seriesTypes, a11ySettings, } = this.props; @@ -171,9 +167,6 @@ class XYChartComponent extends React.Component { return null; } - const chartSeriesTypes = - seriesTypes.size > 1 ? `Mixed chart: ${[...seriesTypes].join(' and ')} chart` : `${[...seriesTypes]} chart`; - return (
{ // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role role="presentation" > -
-
+
); @@ -251,8 +235,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { annotationDimensions: new Map(), annotationSpecs: [], panelGeoms: [], - seriesTypes: new Set(), - a11ySettings: DEFAULT_A11_SETTINGS, + a11ySettings: DEFAULT_A11Y_SETTINGS, }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -282,7 +265,6 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { annotationDimensions: computeAnnotationDimensionsSelector(state), annotationSpecs: getAnnotationSpecsSelector(state), panelGeoms: computePanelsSelectors(state), - seriesTypes: getSeriesTypes(state), a11ySettings: getA11ySettingsSelector(state), }; }; diff --git a/src/chart_types/xy_chart/state/chart_state.accessibility.test.ts b/src/chart_types/xy_chart/state/chart_state.accessibility.test.ts index b07e9fbf63..29d82bc4dc 100644 --- a/src/chart_types/xy_chart/state/chart_state.accessibility.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.accessibility.test.ts @@ -22,7 +22,7 @@ import { Store } from 'redux'; import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; import { MockStore } from '../../../mocks/store/store'; import { GlobalChartState } from '../../../state/chart_state'; -import { DEFAULT_A11_SETTINGS } from '../../../state/selectors/get_accessibility_config'; +import { DEFAULT_A11Y_SETTINGS } from '../../../state/selectors/get_accessibility_config'; import { getSettingsSpecSelector } from '../../../state/selectors/get_settings_specs'; describe('test accessibility prop defaults', () => { @@ -53,7 +53,7 @@ describe('test accessibility prop defaults', () => { } = getSettingsSpecSelector(state); expect(ariaDescription).toBeUndefined(); expect(ariaUseDefaultSummary).toBeTrue(); - expect(ariaLabelHeadingLevel).toBe(DEFAULT_A11_SETTINGS.labelHeadingLevel); + expect(ariaLabelHeadingLevel).toBe(DEFAULT_A11Y_SETTINGS.labelHeadingLevel); expect(ariaLabel).toBeUndefined(); expect(ariaLabelledBy).toBeUndefined(); }); diff --git a/src/chart_types/xy_chart/state/chart_state.tsx b/src/chart_types/xy_chart/state/chart_state.tsx index d1a5587a2b..58ae89ce5d 100644 --- a/src/chart_types/xy_chart/state/chart_state.tsx +++ b/src/chart_types/xy_chart/state/chart_state.tsx @@ -35,6 +35,7 @@ import { Highlighter } from '../renderer/dom/highlighter'; import { computeChartDimensionsSelector } from './selectors/compute_chart_dimensions'; import { computeLegendSelector } from './selectors/compute_legend'; import { getBrushAreaSelector } from './selectors/get_brush_area'; +import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description'; import { getPointerCursorSelector } from './selectors/get_cursor_pointer'; import { getDebugStateSelector } from './selectors/get_debug_state'; import { getHighlightedValuesSelector } from './selectors/get_highlighted_values'; @@ -159,4 +160,8 @@ export class XYAxisChartState implements InternalChartState { getDebugState(globalState: GlobalChartState) { return getDebugStateSelector(globalState); } + + getChartTypeDescription(globalState: GlobalChartState) { + return getChartTypeDescriptionSelector(globalState); + } } diff --git a/src/chart_types/xy_chart/state/selectors/get_series_types.ts b/src/chart_types/xy_chart/state/selectors/get_chart_type_description.ts similarity index 71% rename from src/chart_types/xy_chart/state/selectors/get_series_types.ts rename to src/chart_types/xy_chart/state/selectors/get_chart_type_description.ts index e6ce67d61c..b491bb48d5 100644 --- a/src/chart_types/xy_chart/state/selectors/get_series_types.ts +++ b/src/chart_types/xy_chart/state/selectors/get_chart_type_description.ts @@ -24,11 +24,10 @@ import { SeriesType } from '../../utils/specs'; import { getSeriesSpecsSelector } from './get_specs'; /** @internal */ -export const getSeriesTypes = createCachedSelector( - [getSeriesSpecsSelector], - (specs): Set => { - const seriesTypes = new Set(); - specs.forEach((value) => seriesTypes.add(value.seriesType)); - return seriesTypes; - }, -)(getChartIdSelector); +export const getChartTypeDescriptionSelector = createCachedSelector([getSeriesSpecsSelector], (specs): string => { + const seriesTypes = new Set(); + specs.forEach((value) => seriesTypes.add(value.seriesType)); + const chartSeriesTypes = + seriesTypes.size > 1 ? `Mixed chart: ${[...seriesTypes].join(' and ')} chart` : `${[...seriesTypes]} chart`; + return chartSeriesTypes; +})(getChartIdSelector); diff --git a/src/components/__snapshots__/chart.test.tsx.snap b/src/components/__snapshots__/chart.test.tsx.snap index 88bb981d35..e86c6226e4 100644 --- a/src/components/__snapshots__/chart.test.tsx.snap +++ b/src/components/__snapshots__/chart.test.tsx.snap @@ -54,7 +54,7 @@ exports[`Chart should render the legend name test 1`] = ` - + @@ -69,21 +69,27 @@ exports[`Chart should render the legend name test 1`] = ` - +
-
-
+ + +
+ + + +
+
+ Chart type: +
+
+ bar chart +
+
+
+
+
+
diff --git a/src/components/accessibility/description.tsx b/src/components/accessibility/description.tsx index d9e4853153..c51c2cc2e0 100644 --- a/src/components/accessibility/description.tsx +++ b/src/components/accessibility/description.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { A11ySettings } from '../../state/selectors/get_accessibility_config'; /** @internal */ -export function Description(props: A11ySettings) { +export function ScreenReaderDescription(props: A11ySettings) { if (!props.description) return null; return

{props.description}

; } diff --git a/src/components/accessibility/index.ts b/src/components/accessibility/index.ts new file mode 100644 index 0000000000..c4b3e8087d --- /dev/null +++ b/src/components/accessibility/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* @internal */ +export { ScreenReaderSummary } from './screen_reader_summary'; diff --git a/src/components/accessibility/label.tsx b/src/components/accessibility/label.tsx index 6eb8feb79b..151ace8cba 100644 --- a/src/components/accessibility/label.tsx +++ b/src/components/accessibility/label.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { A11ySettings } from '../../state/selectors/get_accessibility_config'; /** @internal */ -export function Label(props: A11ySettings) { +export function ScreenReaderLabel(props: A11ySettings) { if (!props.label) return null; const Heading = props.labelHeadingLevel; return {props.label}; diff --git a/src/components/accessibility/screen_reader_summary.tsx b/src/components/accessibility/screen_reader_summary.tsx new file mode 100644 index 0000000000..a2efd1a55a --- /dev/null +++ b/src/components/accessibility/screen_reader_summary.tsx @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo } from 'react'; +import { connect } from 'react-redux'; + +import { GlobalChartState } from '../../state/chart_state'; +import { + A11ySettings, + DEFAULT_A11Y_SETTINGS, + getA11ySettingsSelector, +} from '../../state/selectors/get_accessibility_config'; +import { getChartTypeDescriptionSelector } from '../../state/selectors/get_chart_type_description'; +import { getInternalIsInitializedSelector, InitStatus } from '../../state/selectors/get_internal_is_intialized'; +import { ScreenReaderDescription } from './description'; +import { ScreenReaderLabel } from './label'; +import { ScreenReaderTypes } from './types'; + +interface ScreenReaderSummaryStateProps { + a11ySettings: A11ySettings; + chartTypeDescription: string; +} + +const ScreenReaderSummaryComponent = ({ a11ySettings, chartTypeDescription }: ScreenReaderSummaryStateProps) => { + return ( +
+ + + +
+ ); +}; + +const DEFAULT_SCREEN_READER_SUMMARY = { + a11ySettings: DEFAULT_A11Y_SETTINGS, + chartTypeDescription: '', +}; + +const mapStateToProps = (state: GlobalChartState): ScreenReaderSummaryStateProps => { + if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) { + return DEFAULT_SCREEN_READER_SUMMARY; + } + return { + chartTypeDescription: getChartTypeDescriptionSelector(state), + a11ySettings: getA11ySettingsSelector(state), + }; +}; + +/** @internal */ +export const ScreenReaderSummary = memo(connect(mapStateToProps)(ScreenReaderSummaryComponent)); diff --git a/src/components/accessibility/types.tsx b/src/components/accessibility/types.tsx new file mode 100644 index 0000000000..aca5c91d40 --- /dev/null +++ b/src/components/accessibility/types.tsx @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { A11ySettings } from '../../state/selectors/get_accessibility_config'; + +interface ScreenReaderTypesProps { + chartTypeDescription: string; +} + +/** @internal */ +export function ScreenReaderTypes(props: A11ySettings & ScreenReaderTypesProps) { + if (!props.defaultSummaryId) return null; + return ( +
+
Chart type:
+
{props.chartTypeDescription}
+
+ ); +} diff --git a/src/specs/constants.ts b/src/specs/constants.ts index f39a8f92eb..fa1f49f1c7 100644 --- a/src/specs/constants.ts +++ b/src/specs/constants.ts @@ -156,6 +156,6 @@ export const DEFAULT_SETTINGS_SPEC: SettingsSpec = { brushAxis: BrushAxis.X, minBrushDelta: 2, ariaUseDefaultSummary: true, - ariaLabelHeadingLevel: 'h2', + ariaLabelHeadingLevel: 'p', ...DEFAULT_LEGEND_CONFIG, }; diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index 781edefc2d..4999fa86a8 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -153,6 +153,11 @@ export interface InternalChartState { * @param globalState */ getDebugState(globalState: GlobalChartState): DebugState; + + /** + * Get the series types for the screen reader summary component + */ + getChartTypeDescription(globalState: GlobalChartState): string; } /** @internal */ diff --git a/src/state/selectors/get_accessibility_config.ts b/src/state/selectors/get_accessibility_config.ts index 7730e0a584..b6d0ae528a 100644 --- a/src/state/selectors/get_accessibility_config.ts +++ b/src/state/selectors/get_accessibility_config.ts @@ -40,7 +40,7 @@ export type A11ySettings = { }; /** @internal */ -export const DEFAULT_A11_SETTINGS: A11ySettings = { +export const DEFAULT_A11Y_SETTINGS: A11ySettings = { labelHeadingLevel: DEFAULT_SETTINGS_SPEC.ariaLabelHeadingLevel, }; @@ -63,7 +63,7 @@ export const getA11ySettingsSelector = createCachedSelector( labelId: ariaLabelledBy ?? (ariaLabel && `${chartId}--label`), labelHeadingLevel: isValidHeadingLevel(ariaLabelHeadingLevel) ? ariaLabelHeadingLevel - : DEFAULT_A11_SETTINGS.labelHeadingLevel, + : DEFAULT_A11Y_SETTINGS.labelHeadingLevel, // don't use a description if ariaDescribedBy id is provided description: ariaDescribedBy ? undefined : ariaDescription, // concat all the ids diff --git a/src/state/selectors/get_chart_type_description.ts b/src/state/selectors/get_chart_type_description.ts new file mode 100644 index 0000000000..771a9b26c9 --- /dev/null +++ b/src/state/selectors/get_chart_type_description.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { GlobalChartState } from '../chart_state'; + +/** @internal */ +export const getChartTypeDescriptionSelector = (state: GlobalChartState): string => { + if (state.internalChartState) { + return state.internalChartState.getChartTypeDescription(state); + } + // need to return something so there is always a string returned + return 'unknown chart type'; +};