This commit is contained in:
Rachael Sewell 2024-09-26 21:35:57 +00:00 коммит произвёл GitHub
Родитель ec66700365
Коммит 63e9b88c1f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 20 добавлений и 60 удалений

Просмотреть файл

@ -262,7 +262,6 @@ scroll_button:
scroll_to_top: Scroll to top
popovers:
role_description: hovercard link
keyboard_shortcut_description: Press alt+up to activate
alerts:
NOTE: Note
IMPORTANT: Important

Просмотреть файл

@ -262,7 +262,6 @@ scroll_button:
scroll_to_top: Scroll to top
popovers:
role_description: hovercard link
keyboard_shortcut_description: Press alt+up to activate
alerts:
NOTE: Note
IMPORTANT: Important

Просмотреть файл

@ -286,16 +286,27 @@ test.describe('hover cards', () => {
).not.toBeVisible()
})
test('internal links get a aria-roledescription and aria-describedby', async ({ page }) => {
test('able to use Esc to close hovercard', async ({ page }) => {
await page.goto('/pages/quickstart')
const link = page.locator('#article-contents').getByRole('link', { name: 'Start your journey' })
await expect(link).toHaveAttribute('aria-roledescription', 'hovercard link')
// The link gets a `aria-describedby="...ID..."` attribute that points to
// another element in the DOM that has the description text.
const id = 'popover-describedby'
await expect(link).toHaveAttribute('aria-describedby', id)
await expect(page.locator(`#${id}`)).toHaveText('Press alt+up to activate')
// hover over a link and check for intro content from hovercard
await page
.locator('#article-contents')
.getByRole('link', { name: 'Start your journey' })
.hover()
await expect(
page.getByText(
'Get started using HubGit to manage Git repositories and collaborate with others.',
),
).toBeVisible()
// click the Esc key to close the hovercard
await page.keyboard.press('Escape')
await expect(
page.getByText(
'Get started using GitHub to manage Git repositories and collaborate with others.',
),
).not.toBeVisible()
})
})

Просмотреть файл

@ -1,6 +1,4 @@
import { useEffect } from 'react'
import { useTranslation } from 'src/languages/components/useTranslation'
import { useRouter } from 'next/router'
// We postpone the initial delay a bit in case the user didn't mean to
// hover over the link. Perhaps they just dragged the mouse over on their
@ -36,14 +34,6 @@ let currentlyOpen: HTMLLinkElement | null = null
// change according to the popover's true height. But this can cause a flicker.
const BOUNDING_TOP_MARGIN = 300
// All links that should have a hover card also get a
// `aria-describedby="..."`. That ID is used to look up another DOM
// element, that has a `visually-hidden` class. The value if the ID
// isn't very important as long as it connects.
// Note; at the moment this value is duplicated in the Playwright
// tests because of trying to extract the value of `aria-describedby`.
const DESCRIBEDBY_ELEMENT_ID = 'popover-describedby'
// used to identify the first focusable element in the hover card
const FIRST_LINK_ID = '_hc_first_focusable'
const TITLE_ID = '_hc_title'
@ -70,7 +60,7 @@ function getOrCreatePopoverGlobal() {
// Semantics for the hovercard so SR users are aware they're about to be
// focus trapped
wrapper.setAttribute('role', 'dialog')
wrapper.setAttribute('role', 'region')
wrapper.setAttribute('aria-modal', 'true')
wrapper.setAttribute('aria-label', 'user hovercard')
wrapper.setAttribute('aria-labelledby', TITLE_ID)
@ -202,28 +192,6 @@ function getOrCreatePopoverGlobal() {
return popoverGlobal
}
function getOrCreateDescribeByElement() {
let element = document.querySelector<HTMLParagraphElement>(`#${DESCRIBEDBY_ELEMENT_ID}`)
if (!element) {
element = document.createElement('p')
element.id = DESCRIBEDBY_ELEMENT_ID
element.classList.add('visually-hidden')
// "All page content should be contained by landmarks"
// https://dequeuniversity.com/rules/axe/4.7/region
// The element that we use for the `aria-describedby` attribute
// needs to exist in the DOM inside a landmark. For example
// `<div role="footer">`. In our case we use our
// `<main id="main-content">` element.
// We "know" that this querySelector() query will always find a
// valid element, but it's theoretically not perfectly true, so we have to
// use a fallback.
const main = document.querySelector<HTMLDivElement>('main') || document.body
main.appendChild(element)
}
return element
}
function popoverWrap(element: HTMLLinkElement, filledCallback?: (popover: HTMLDivElement) => void) {
if (element.parentElement && element.parentElement.classList.contains('Popover')) {
return
@ -474,16 +442,6 @@ function popoverHide() {
let lastFocussedLink: HTMLLinkElement | null = null
export function LinkPreviewPopover() {
const { t } = useTranslation('popovers')
const { locale } = useRouter()
useEffect(() => {
const element = getOrCreateDescribeByElement()
if (element) {
element.textContent = t('keyboard_shortcut_description')
}
}, [locale])
// This is to track if the user entirely tabs out of the window.
// For example if they go to the address bar.
useEffect(() => {
@ -587,13 +545,6 @@ export function LinkPreviewPopover() {
link.addEventListener('mouseover', showPopover)
link.addEventListener('mouseout', hidePopover)
link.addEventListener('keydown', keyboardHandler)
if (!link.getAttribute('aria-roledescription')) {
link.setAttribute('aria-roledescription', t('role_description'))
}
if (!link.getAttribute('aria-describedby')) {
link.setAttribute('aria-describedby', DESCRIBEDBY_ELEMENT_ID)
}
}
document.addEventListener('keydown', escapeHandler)