From f1c3ebb8e2b4c8d68d4bd89e22e619e6b1ce37e9 Mon Sep 17 00:00:00 2001 From: Kallyan Singha Date: Fri, 17 Jan 2025 03:48:25 +0530 Subject: [PATCH] fix: block-manager a11y fix for block list --- .../src/components/block-manager/category.js | 167 +++++++++--------- .../src/components/block-manager/index.js | 46 ++++- 2 files changed, 130 insertions(+), 83 deletions(-) diff --git a/packages/block-editor/src/components/block-manager/category.js b/packages/block-editor/src/components/block-manager/category.js index 79d5896b4502e4..c447eec5797f52 100644 --- a/packages/block-editor/src/components/block-manager/category.js +++ b/packages/block-editor/src/components/block-manager/category.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCallback } from '@wordpress/element'; +import { useCallback, forwardRef } from '@wordpress/element'; import { useInstanceId } from '@wordpress/compose'; import { CheckboxControl } from '@wordpress/components'; @@ -9,94 +9,97 @@ import { CheckboxControl } from '@wordpress/components'; * Internal dependencies */ import BlockTypesChecklist from './checklist'; +const a = forwardRef( () => { + return a; +} ); -function BlockManagerCategory( { - title, - blockTypes, - selectedBlockTypes, - onChange, -} ) { - const instanceId = useInstanceId( BlockManagerCategory ); +const BlockManagerCategory = forwardRef( + ( { title, blockTypes, selectedBlockTypes, onChange }, ref ) => { + const instanceId = useInstanceId( BlockManagerCategory ); - const toggleVisible = useCallback( - ( blockType, nextIsChecked ) => { - if ( nextIsChecked ) { - onChange( [ ...selectedBlockTypes, blockType ] ); - } else { - onChange( - selectedBlockTypes.filter( - ( { name } ) => name !== blockType.name - ) - ); - } - }, - [ selectedBlockTypes, onChange ] - ); + const toggleVisible = useCallback( + ( blockType, nextIsChecked ) => { + if ( nextIsChecked ) { + onChange( [ ...selectedBlockTypes, blockType ] ); + } else { + onChange( + selectedBlockTypes.filter( + ( { name } ) => name !== blockType.name + ) + ); + } + }, + [ selectedBlockTypes, onChange ] + ); - const toggleAllVisible = useCallback( - ( nextIsChecked ) => { - if ( nextIsChecked ) { - onChange( [ - ...selectedBlockTypes, - ...blockTypes.filter( - ( blockType ) => - ! selectedBlockTypes.find( - ( { name } ) => name === blockType.name - ) - ), - ] ); - } else { - onChange( - selectedBlockTypes.filter( - ( selectedBlockType ) => - ! blockTypes.find( - ( { name } ) => name === selectedBlockType.name - ) - ) - ); - } - }, - [ blockTypes, selectedBlockTypes, onChange ] - ); + const toggleAllVisible = useCallback( + ( nextIsChecked ) => { + if ( nextIsChecked ) { + onChange( [ + ...selectedBlockTypes, + ...blockTypes.filter( + ( blockType ) => + ! selectedBlockTypes.find( + ( { name } ) => name === blockType.name + ) + ), + ] ); + } else { + onChange( + selectedBlockTypes.filter( + ( selectedBlockType ) => + ! blockTypes.find( + ( { name } ) => + name === selectedBlockType.name + ) + ) + ); + } + }, + [ blockTypes, selectedBlockTypes, onChange ] + ); - if ( ! blockTypes.length ) { - return null; - } + if ( ! blockTypes.length ) { + return null; + } - const checkedBlockNames = blockTypes - .map( ( { name } ) => name ) - .filter( ( type ) => - ( selectedBlockTypes ?? [] ).some( - ( selectedBlockType ) => selectedBlockType.name === type - ) - ); + const checkedBlockNames = blockTypes + .map( ( { name } ) => name ) + .filter( ( type ) => + ( selectedBlockTypes ?? [] ).some( + ( selectedBlockType ) => selectedBlockType.name === type + ) + ); - const titleId = 'block-editor-block-manager__category-title-' + instanceId; + const titleId = + 'block-editor-block-manager__category-title-' + instanceId; - const isAllChecked = checkedBlockNames.length === blockTypes.length; - const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0; + const isAllChecked = checkedBlockNames.length === blockTypes.length; + const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0; - return ( -
- { title } } - /> - -
- ); -} + return ( +
+ { title } } + /> + +
+ ); + } +); export default BlockManagerCategory; diff --git a/packages/block-editor/src/components/block-manager/index.js b/packages/block-editor/src/components/block-manager/index.js index 30d10e67040c71..c9a8993673191e 100644 --- a/packages/block-editor/src/components/block-manager/index.js +++ b/packages/block-editor/src/components/block-manager/index.js @@ -5,7 +5,7 @@ import { store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { SearchControl, Button } from '@wordpress/components'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useState, useRef } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; import { speak } from '@wordpress/a11y'; @@ -35,6 +35,48 @@ export default function BlockManager( { isMatchingSearchTerm: select( blocksStore ).isMatchingSearchTerm, }; }, [] ); + const blockManagerCategoryRef = useRef( null ); + + useEffect( () => { + const container = document.querySelector( + '.components-modal__content' + ); + const stickyElement = blockManagerCategoryRef.current; + + if ( ! container || ! stickyElement ) { + return; + } + + const handleFocusIn = ( event ) => { + const focusedElement = event.target; + + // Check if the focused element is within the container + if ( container.contains( focusedElement ) ) { + const stickyBottom = 250; + const focusedRect = focusedElement.getBoundingClientRect(); + + // Calculate the desired scroll position + if ( + focusedRect.top < stickyBottom && + container.scrollTop > 190 + ) { + const offset = + container.scrollTop - stickyElement.offsetHeight; + container.scrollTo( { + top: offset, + behavior: 'smooth', + } ); + } + } + }; + + container.addEventListener( 'focusin', handleFocusIn ); + + // Cleanup the event listener + return () => { + container.removeEventListener( 'focusin', handleFocusIn ); + }; + }, [] ); function enableAllBlockTypes() { onChange( blockTypes ); @@ -103,6 +145,7 @@ export default function BlockManager( { ) } { categories.map( ( category ) => ( ) ) } ! category