From 8f45106b02d67253d9e66fc2dd93cddcffaa1982 Mon Sep 17 00:00:00 2001 From: Melloware Date: Mon, 29 Jan 2024 16:12:51 -0500 Subject: [PATCH] Fix #5849: Splitter improve ARIA accessibility (#5852) --- components/doc/splitter/accessibilitydoc.js | 18 +++++ components/lib/splitter/Splitter.js | 80 +++++++++++++++---- .../__snapshots__/Splitter.spec.js.snap | 61 ++++++++++++-- components/lib/splitter/splitter.d.ts | 14 +++- 4 files changed, 151 insertions(+), 22 deletions(-) diff --git a/components/doc/splitter/accessibilitydoc.js b/components/doc/splitter/accessibilitydoc.js index 9ddcc6f417..3d2d11e50c 100644 --- a/components/doc/splitter/accessibilitydoc.js +++ b/components/doc/splitter/accessibilitydoc.js @@ -50,6 +50,24 @@ export function AccessibilityDoc() { Moves a vertical splitter to the right. + + + home + + Maximizes the primary panel. + + + + end + + Minimizes the primary panel. + + + + enter + + Toggles the primary panel between minimum and maximum sizes. + diff --git a/components/lib/splitter/Splitter.js b/components/lib/splitter/Splitter.js index 628463512d..e4c2577d93 100644 --- a/components/lib/splitter/Splitter.js +++ b/components/lib/splitter/Splitter.js @@ -1,8 +1,8 @@ import * as React from 'react'; import { PrimeReactContext } from '../api/Api'; import { useHandleStyle } from '../componentbase/ComponentBase'; -import { useEventListener, useMergeProps } from '../hooks/Hooks'; -import { DomHandler, ObjectUtils, classNames } from '../utils/Utils'; +import { useEventListener, useMergeProps, useMountEffect } from '../hooks/Hooks'; +import { DomHandler, ObjectUtils, UniqueComponentId, classNames } from '../utils/Utils'; import { SplitterBase, SplitterPanelBase } from './SplitterBase'; export const SplitterPanel = () => {}; @@ -13,6 +13,7 @@ export const Splitter = React.memo( const context = React.useContext(PrimeReactContext); const props = SplitterBase.getProps(inProps, context); + const idState = React.useRef(''); const elementRef = React.useRef(null); const gutterRef = React.useRef(); const gutterRefs = React.useRef({}); @@ -183,13 +184,7 @@ export const Splitter = React.memo( newNextPanelSize = nextPanelSize.current - newPos; } - if (validateResize(newPrevPanelSize, newNextPanelSize)) { - prevPanelSizeNew.current = newPrevPanelSize; - nextPanelSizeNew.current = newNextPanelSize; - prevPanelElement.current.style.flexBasis = 'calc(' + newPrevPanelSize + '% - ' + (props.children.length - 1) * props.gutterSize + 'px)'; - nextPanelElement.current.style.flexBasis = 'calc(' + newNextPanelSize + '% - ' + (props.children.length - 1) * props.gutterSize + 'px)'; - prevSize.current = parseFloat(newPrevPanelSize).toFixed(4); - } + resizePanel(prevPanelIndex.current, newPrevPanelSize, newNextPanelSize); }; const onResizeEnd = (event) => { @@ -225,6 +220,8 @@ export const Splitter = React.memo( }; const onGutterKeyDown = (event, index) => { + const minSize = (props.children[index].props && props.children[index].props.minSize) || 0; + switch (event.code) { case 'ArrowLeft': { if (props.layout === 'horizontal') { @@ -262,12 +259,53 @@ export const Splitter = React.memo( break; } + case 'Home': { + resizePanel(index, 100, minSize); + + event.preventDefault(); + break; + } + + case 'End': { + resizePanel(index, minSize, 100); + + event.preventDefault(); + break; + } + + case 'Enter': { + if (prevSize.current > 99) { + resizePanel(index, minSize, 100); + } else { + resizePanel(index, 100, minSize); + } + + event.preventDefault(); + break; + } + default: //no op break; } }; + const resizePanel = (index, newPrevPanelSize, newNextPanelSize) => { + prevPanelIndex.current = index; + gutterRef.current = gutterRefs.current[index]; + size.current = horizontal ? DomHandler.getWidth(elementRef.current) : DomHandler.getHeight(elementRef.current); + prevPanelElement.current = gutterRef.current.previousElementSibling; + nextPanelElement.current = gutterRef.current.nextElementSibling; + + if (validateResize(newPrevPanelSize, newNextPanelSize)) { + prevPanelSizeNew.current = newPrevPanelSize; + nextPanelSizeNew.current = newNextPanelSize; + prevPanelElement.current.style.flexBasis = 'calc(' + newPrevPanelSize + '% - ' + (props.children.length - 1) * props.gutterSize + 'px)'; + nextPanelElement.current.style.flexBasis = 'calc(' + newNextPanelSize + '% - ' + (props.children.length - 1) * props.gutterSize + 'px)'; + prevSize.current = parseFloat(newPrevPanelSize).toFixed(4); + } + }; + const repeat = (event, index, step) => { onResizeStart(event, index, true); onResize(event, step, true); @@ -313,6 +351,12 @@ export const Splitter = React.memo( getElement: () => elementRef.current })); + useMountEffect(() => { + if (elementRef.current) { + idState.current = UniqueComponentId(); + } + }); + React.useEffect(() => { const panelElements = [...elementRef.current.children].filter((child) => DomHandler.getAttribute(child, 'data-pc-section') === 'splitterpanel.root'); @@ -333,6 +377,7 @@ export const Splitter = React.memo( }, [restoreState, isStateful]); const createPanel = (panel, index) => { + const panelId = getPanelProp(panel, 'id') || `${idState.current}_${index}`; const panelClassName = classNames(getPanelProp(panel, 'className'), cx('panel.root')); const gutterProps = mergeProps( @@ -346,17 +391,24 @@ export const Splitter = React.memo( onTouchStart: (event) => onGutterTouchStart(event, index), onTouchMove: (event) => onGutterTouchMove(event), onTouchEnd: (event) => onGutterTouchEnd(event), - 'data-p-splitter-gutter-resizing': false + 'data-p-splitter-gutter-resizing': false, + role: 'splitter' }, ptm('gutter') ); const gutterHandlerProps = mergeProps( { - tabIndex: 0, + tabIndex: getPanelProp(panel, 'tabIndex') || 0, className: cx('gutterHandler'), - 'aria-orientation': props.layout, - 'aria-valuenow': prevSize.current + 'aria-orientation': props.layout === 'horizontal' ? 'vertical' : 'horizontal', + 'aria-controls': panelId, + 'aria-label': getPanelProp(panel, 'aria-label'), + 'aria-labelledby': getPanelProp(panel, 'aria-labelledby'), + 'aria-valuenow': prevSize.current, + 'aria-valuetext': parseFloat(prevSize.current).toFixed(0) + '%', + 'aria-valuemin': getPanelProp(panel, 'minSize') || '0', + 'aria-valuemax': '100' }, ptm('gutterHandler') ); @@ -372,7 +424,7 @@ export const Splitter = React.memo( const rootProps = mergeProps( { key: index, - id: getPanelProp(panel, 'id'), + id: panelId, className: panelClassName, style: { ...getPanelProp(panel, 'style'), flexBasis }, role: 'presentation', diff --git a/components/lib/splitter/__snapshots__/Splitter.spec.js.snap b/components/lib/splitter/__snapshots__/Splitter.spec.js.snap index 44920578f0..c7740b04bc 100644 --- a/components/lib/splitter/__snapshots__/Splitter.spec.js.snap +++ b/components/lib/splitter/__snapshots__/Splitter.spec.js.snap @@ -13,6 +13,7 @@ exports[`Splitter Nested 1`] = ` class="flex align-items-center justify-content-center p-splitter-panel" data-p-splitter-panel-nested="false" data-pc-section="splitterpanel.root" + id="pr_id_5_0" role="presentation" style="flex-basis: calc(20% - 4px);" > @@ -22,11 +23,16 @@ exports[`Splitter Nested 1`] = ` class="p-splitter-gutter" data-p-splitter-gutter-resizing="false" data-pc-section="gutter" + role="splitter" style="width: 4px;" >