Make editor popover toolbar keyboard focusable (#25169)

* Remove redundant class conflicting with focus styling

* Make the toolbar in the popover focusable via keyboard

* Focus to the first context menu item via keyboard only

GitOrigin-RevId: 7d3e2af4ba96654b5b2312b3999483c2a439b406
This commit is contained in:
Rebeka Dekany
2025-04-30 08:05:25 +00:00
committed by Copybot
parent 14c82ac94d
commit 2731ffaf10
7 changed files with 107 additions and 23 deletions
@@ -21,7 +21,7 @@ export const ToolbarButtonMenu: FC<{
const button = (
<button
type="button"
className="ol-cm-toolbar-button btn"
className="ol-cm-toolbar-button"
aria-label={label}
onMouseDown={event => {
event.preventDefault()
@@ -1,4 +1,5 @@
import { FC, useRef } from 'react'
import { FC, useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import classnames from 'classnames'
import MaterialIcon from '@/shared/components/material-icon'
import { useCodeMirrorViewContext } from '../codemirror-context'
@@ -11,7 +12,10 @@ export const ToolbarOverflow: FC<{
setOverflowOpen: (open: boolean) => void
overflowRef?: React.Ref<HTMLDivElement>
}> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
const { t } = useTranslation()
const buttonRef = useRef<HTMLButtonElement>(null)
const keyboardInputRef = useRef(false)
const view = useCodeMirrorViewContext()
const className = classnames(
@@ -22,6 +26,46 @@ export const ToolbarOverflow: FC<{
}
)
// A11y - Move the focus inside the popover to the first toolbar button when it opens
const handlePopoverFocus = useCallback(() => {
if (keyboardInputRef.current) {
const firstToolbarItem = document.querySelector(
'#popover-toolbar-overflow .ol-cm-toolbar-overflow button:not([disabled])'
) as HTMLButtonElement | null
if (firstToolbarItem) {
firstToolbarItem.focus()
}
}
}, [])
const handleKeyDown = useCallback(() => {
keyboardInputRef.current = true
}, [])
const handleMouseDown = useCallback(() => {
keyboardInputRef.current = false
}, [])
useEffect(() => {
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('mousedown', handleMouseDown)
return () => {
document.removeEventListener('keydown', handleKeyDown)
document.removeEventListener('mousedown', handleMouseDown)
}
}, [handleKeyDown, handleMouseDown])
// A11y - Move the focus back to the trigger when the popover is dismissed
const handleCloseAndReturnFocus = useCallback(() => {
setOverflowOpen(false)
if (keyboardInputRef.current && buttonRef.current) {
buttonRef.current.focus()
}
}, [setOverflowOpen])
return (
<>
<button
@@ -29,7 +73,9 @@ export const ToolbarOverflow: FC<{
type="button"
id="toolbar-more"
className={className}
aria-label="More"
aria-label={t('more_editor_toolbar_item')}
aria-expanded={overflowOpen}
aria-controls="popover-toolbar-overflow"
onMouseDown={event => {
event.preventDefault()
event.stopPropagation()
@@ -49,9 +95,14 @@ export const ToolbarOverflow: FC<{
// containerPadding={0}
transition
rootClose
onHide={() => setOverflowOpen(false)}
onHide={handleCloseAndReturnFocus}
onEntered={handlePopoverFocus}
>
<OLPopover id="popover-toolbar-overflow" ref={overflowRef}>
<OLPopover
id="popover-toolbar-overflow"
ref={overflowRef}
role="toolbar"
>
<div className="ol-cm-toolbar-overflow">{children}</div>
</OLPopover>
</OLOverlay>
@@ -36,7 +36,7 @@ export const LegacyTableDropdown = memo(() => {
>
<button
type="button"
className="ol-cm-toolbar-button btn"
className="ol-cm-toolbar-button"
aria-label={t('toolbar_insert_table')}
onMouseDown={event => {
event.preventDefault()