diff --git a/src/attribute-editor/__tests__/attribute-editor.test.tsx b/src/attribute-editor/__tests__/attribute-editor.test.tsx index 232c0389d9..eeecde28d0 100644 --- a/src/attribute-editor/__tests__/attribute-editor.test.tsx +++ b/src/attribute-editor/__tests__/attribute-editor.test.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import createWrapper, { AttributeEditorWrapper } from '../../../lib/components/test-utils/dom'; import AttributeEditor, { AttributeEditorProps } from '../../../lib/components/attribute-editor'; +import Input from '../../../lib/components/input'; interface Item { key: string; @@ -322,4 +323,35 @@ describe('Attribute Editor', () => { } }); }); + + describe('a11y', () => { + test('row has role group and aria-labelledby referring to first control label and content', () => { + const wrapper = renderAttributeEditor({ + ...defaultProps, + definition: [ + { + label: 'Key label', + info: 'Key info', + control: item => , + }, + { + label: 'Value label', + info: 'Value info', + control: item => , + }, + ], + }); + const [labelId, inputId] = wrapper + .findRow(1)! + .find('[role="group"]')! + .getElement() + .getAttribute('aria-labelledby')! + .split(' '); + const label = + wrapper.getElement().querySelector(`#${labelId}`)!.textContent + + ' ' + + wrapper.getElement().querySelector(`#${inputId}`)!.getAttribute('value'); + expect(label).toBe('Key label k1'); + }); + }); }); diff --git a/src/attribute-editor/row.tsx b/src/attribute-editor/row.tsx index ebb27ed3c3..19a26c9f17 100644 --- a/src/attribute-editor/row.tsx +++ b/src/attribute-editor/row.tsx @@ -11,6 +11,7 @@ import { fireNonCancelableEvent, NonCancelableEventHandler } from '../internal/e import InternalGrid from '../grid/internal'; import { InternalButton } from '../button/internal'; import clsx from 'clsx'; +import { generateUniqueId } from '../internal/hooks/use-unique-id'; const Divider = () => ; @@ -55,44 +56,53 @@ export const Row = React.memo( fireNonCancelableEvent(onRemoveButtonClick, { itemIndex: index }); }, [onRemoveButtonClick, index]); + const firstControlId = generateUniqueId('first-control-id-'); + return ( - - - {definition.map(({ info, label, constraintText, errorText, control }, defIndex) => ( - 0} - > - {render(item, index, control)} - - ))} - - {removable && ( - - { - removeButtonRefs[index] = ref ?? undefined; - }} - onClick={handleRemoveClick} - > - {removeButtonText} - - - )} - +
+ + + {definition.map(({ info, label, constraintText, errorText, control }, defIndex) => ( + 0} + controlId={defIndex === 0 ? firstControlId : undefined} + > + {render(item, index, control)} + + ))} + + {removable && ( + + { + removeButtonRefs[index] = ref ?? undefined; + }} + onClick={handleRemoveClick} + > + {removeButtonText} + + + )} + +
{isNarrowViewport && }
);