diff --git a/src-docs/src/components/scroll_to_hash.tsx b/src-docs/src/components/scroll_to_hash.tsx index 23a6bb59b27..678a8494d77 100644 --- a/src-docs/src/components/scroll_to_hash.tsx +++ b/src-docs/src/components/scroll_to_hash.tsx @@ -1,5 +1,6 @@ import { useEffect, useState, FunctionComponent } from 'react'; import { useLocation } from 'react-router-dom'; +import { isTabbable } from 'tabbable'; const ScrollToHash: FunctionComponent = () => { const location = useLocation(); @@ -18,11 +19,23 @@ const ScrollToHash: FunctionComponent = () => { const element = document.getElementById(hash); const headerOffset = 48; if (element) { + // Focus element for keyboard and screen reader users + if (!isTabbable(element)) { + element.tabIndex = -1; + element.addEventListener( + 'blur', + () => element.removeAttribute('tabindex'), + { once: true } + ); + element.focus(); + } + // Scroll to element window.scrollTo({ top: element.offsetTop - headerOffset, behavior: 'smooth', }); } else { + // Scroll back to top of page window.scrollTo({ behavior: 'auto', top: 0, diff --git a/src-docs/src/views/accessibility/accessibility_example.js b/src-docs/src/views/accessibility/accessibility_example.js index fde94031ff8..202d6805b16 100644 --- a/src-docs/src/views/accessibility/accessibility_example.js +++ b/src-docs/src/views/accessibility/accessibility_example.js @@ -13,12 +13,14 @@ import { } from '../../../../src'; import ScreenReaderLive from './screen_reader_live'; +import ScreenReaderLiveFocus from './screen_reader_live_focus'; import ScreenReaderOnly from './screen_reader'; import ScreenReaderFocus from './screen_reader_focus'; import SkipLink from './skip_link'; import StylesHelpers from './styles_helpers'; const screenReaderLiveSource = require('!!raw-loader!./screen_reader_live'); +const screenReaderLiveFocusSource = require('!!raw-loader!./screen_reader_live_focus'); const screenReaderOnlySource = require('!!raw-loader!./screen_reader'); const screenReaderFocusSource = require('!!raw-loader!./screen_reader_focus'); @@ -154,8 +156,50 @@ export const AccessibilityExample = { props: { EuiScreenReaderLive, }, + snippet: ` + +`, demo: , }, + { + text: ( + <> +

Auto-focusing the live region on text change

+

+ The focusRegionOnTextChange prop will + automatically focus the EuiScreenReaderLive{' '} + region (causing screen readers to read out the text content) + whenever children changes. +

+

+ This is primarily useful for announcing navigation or page changes, + when programmatically resetting focus location back to a certain + part of the page (where the EuiScreenReaderLive{' '} + is placed) is desired. +

+

+ + Using a screen reader, click the following navigation links and + notice that when the new page is announced, focus is also set to + the top of the body content. + +

+ + ), + props: { + EuiScreenReaderLive, + }, + snippet: ` + +`, + demo: , + source: [ + { + type: GuideSectionTypes.TSX, + code: screenReaderLiveFocusSource, + }, + ], + }, { title: 'Skip link', source: [ diff --git a/src-docs/src/views/accessibility/screen_reader_live_focus.tsx b/src-docs/src/views/accessibility/screen_reader_live_focus.tsx new file mode 100644 index 00000000000..d06df404f97 --- /dev/null +++ b/src-docs/src/views/accessibility/screen_reader_live_focus.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; + +import { + EuiScreenReaderLive, + EuiPageTemplate, + EuiSideNav, + EuiButton, +} from '../../../../src/components'; +import { htmlIdGenerator } from '../../../../src/services'; + +export default () => { + const idGenerator = htmlIdGenerator('focusRegionOnTextChange'); + + const [pageTitle, setPageTitle] = useState('Home'); + + const sideNav = [ + { + name: 'Example side nav', + id: idGenerator(), + items: [ + { + name: 'Home', + id: idGenerator(), + onClick: () => setPageTitle('Home'), + }, + { + name: 'About', + id: idGenerator(), + onClick: () => setPageTitle('About'), + }, + { + name: 'Docs', + id: idGenerator(), + onClick: () => setPageTitle('Docs'), + }, + { + name: 'Contact', + id: idGenerator(), + onClick: () => setPageTitle('Contact'), + }, + ], + }, + ]; + + return ( + <> + } + pageHeader={{ + iconType: 'logoElastic', + pageTitle: pageTitle, + }} + > + + {pageTitle} + + + Clicking a nav link and then pressing tab should focus this button + + + + ); +}; diff --git a/src-docs/src/views/app_view.js b/src-docs/src/views/app_view.js index 132653906b8..21e61b29828 100644 --- a/src-docs/src/views/app_view.js +++ b/src-docs/src/views/app_view.js @@ -10,6 +10,7 @@ import { EuiPage, EuiPageBody, EuiSkipLink, + EuiScreenReaderLive, } from '../../../src/components'; import { keys } from '../../../src/services'; @@ -69,7 +70,11 @@ export const AppView = ({ children, currentRoute }) => { return ( + + {`${currentRoute.name} - Elastic UI Framework`} + `; +exports[`EuiScreenReaderLive with a static configuration accepts \`focusRegionOnTextChange\` 1`] = ` +
+ +`; + exports[`EuiScreenReaderLive with a static configuration accepts \`role\` 1`] = `