From bc09133fbef10a29061e195f0de10a49cbd23e66 Mon Sep 17 00:00:00 2001 From: Slizhevsky Vladislav Date: Fri, 9 Jan 2026 12:24:51 +0100 Subject: [PATCH 1/8] [UIK-4573][d3-chart] new approach to how a11y module is invoked --- semcore/d3-chart/src/Plot.jsx | 2 +- semcore/d3-chart/src/a11y/PlotA11yModule.tsx | 107 +++++------------- semcore/d3-chart/src/a11y/PlotA11yView.tsx | 46 +++++++- .../src/style/plotA11yModule.shadow.css | 21 ++-- .../src/style/plotA11yView.shadow.css | 39 +++---- 5 files changed, 98 insertions(+), 117 deletions(-) diff --git a/semcore/d3-chart/src/Plot.jsx b/semcore/d3-chart/src/Plot.jsx index 7e1113e514..f7da981fc5 100644 --- a/semcore/d3-chart/src/Plot.jsx +++ b/semcore/d3-chart/src/Plot.jsx @@ -104,7 +104,7 @@ class PlotRoot extends Component { onMouseMove={this.handlerMouseMove} onMouseLeave={this.handlerMouseLeave} aria-label={ariaLabel} - tabIndex={0} + use:tabIndex={-1} data-plot-id={this.plotId} > diff --git a/semcore/d3-chart/src/a11y/PlotA11yModule.tsx b/semcore/d3-chart/src/a11y/PlotA11yModule.tsx index 567c8c7f89..f0d02b397c 100644 --- a/semcore/d3-chart/src/a11y/PlotA11yModule.tsx +++ b/semcore/d3-chart/src/a11y/PlotA11yModule.tsx @@ -1,39 +1,22 @@ +import { Box } from '@semcore/base-components'; import { Root, sstyled } from '@semcore/core'; import { Context as I18nContext, useI18n } from '@semcore/core/lib/utils/enhances/WithI18n'; -import { Box } from '@semcore/flex-box'; import React from 'react'; -import type { DataStructureHints, PartialDataSummarizationConfig } from './hints'; import { normalizeLocale } from './locale'; +import type { A11yViewProps } from './PlotA11yView'; import styles from '../style/plotA11yModule.shadow.css'; import { localizedMessages } from './translations/module/__intergalactic-dynamic-locales'; -let globalWasFocused = false; -let globalNavWithKeyboard = false; +type A11yModuleProps = Omit; -export type A11yViewProps = { - id: string; - data: Record[]; - hints: DataStructureHints; - plotLabel: string; - locale: NavigatorLanguage['language']; - config: PartialDataSummarizationConfig; - - plotRef: React.RefObject; -}; - -export function PlotA11yModule(props: A11yViewProps) { +export function PlotA11yModule(props: A11yModuleProps) { const SPlotA11yModule = Root; - const [wasFocused, setWasFocused] = React.useState(globalWasFocused); - const [navWithKeyboard, setNavWithKeyboard] = React.useState(globalNavWithKeyboard); + const [isOpened, setIsOpened] = React.useState(false); const [plotA11yView, setPlotA11yView] = React.useState<{ Component: React.FC; } | null>(null); - - const hadnleHiddenElementsFocus = React.useCallback(() => { - setWasFocused(true); - setNavWithKeyboard(true); - }, []); + const srButtonRef = React.useRef(null); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); @@ -46,40 +29,7 @@ export function PlotA11yModule(props: A11yViewProps) { const t = useI18n(localizedMessages, locale!); React.useEffect(() => { - if (wasFocused) return; - const focusListener = () => { - globalWasFocused = true; - setWasFocused(true); - }; - - props.plotRef.current?.addEventListener('focus', focusListener); - return () => props.plotRef.current?.removeEventListener('focus', focusListener); - }, [wasFocused, props.plotRef]); - React.useEffect(() => { - if (navWithKeyboard) return; - const keyboardListener = (event: Event) => { - const navigationKeys = [ - 'Tab', - 'ArrowUp', - 'ArrowLeft', - 'ArrowDown', - 'ArrowRight', - 'ArrowUp', - 'ArrowLeft', - ]; - if ('key' in event && navigationKeys.includes((event as KeyboardEvent).key)) { - setNavWithKeyboard(true); - globalNavWithKeyboard = true; - } - }; - document.body?.addEventListener('keydown', keyboardListener); - return () => document.body?.removeEventListener('keydown', keyboardListener); - }, [navWithKeyboard]); - - const shouldDisplayView = wasFocused && navWithKeyboard; - - React.useEffect(() => { - if (!shouldDisplayView) return; + if (!isOpened) return; if (plotA11yView) return; if (loading) return; @@ -95,33 +45,36 @@ export function PlotA11yModule(props: A11yViewProps) { // eslint-disable-next-line no-console console.error(error); setError(error); + setLoading(false); }); - }, [plotA11yView, shouldDisplayView, loading, setLoading]); + }, [plotA11yView, isOpened, loading, setLoading]); if (plotA11yView) { return sstyled(styles)( - , - ) as React.ReactElement; - } - - if (error) { - return sstyled(styles)( - - {t('failed')} - , - ) as React.ReactElement; - } - if (loading) { - return sstyled(styles)( - - {t('loading')} - , + { + setIsOpened(false); + setPlotA11yView(null); + }} + triggerRef={srButtonRef} + locale={locale!} + />, ) as React.ReactElement; } return sstyled(styles)( - - {t('disabled')} + +