Skip to content

Commit 69aab1c

Browse files
authored
fix(AnalyticalTable): fix pop-in a11y and cell-data (#8535)
Fixes #7410
1 parent 9d29b3b commit 69aab1c

5 files changed

Lines changed: 64 additions & 14 deletions

File tree

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,19 @@ describe('AnalyticalTable', () => {
25852585
//popinDisplay: Block
25862586
cy.get('@popinHeader').parent().should('have.css', 'flex-direction', 'column');
25872587

2588+
// a11y: pop-in elements have id + aria-hidden, first cell aria-labelledby includes pop-in IDs
2589+
cy.get('[data-component-name="AnalyticalTablePopinHeaderContainer"]')
2590+
.first()
2591+
.should('have.attr', 'aria-hidden', 'true')
2592+
.and('have.attr', 'id');
2593+
cy.get('[aria-rowindex="2"] [data-is-first-column="true"]')
2594+
.first()
2595+
.then(($cell) => {
2596+
const labelledby = $cell.attr('aria-labelledby');
2597+
expect(labelledby).to.contain('popin-h-friend.name-');
2598+
expect(labelledby).to.contain('popin-v-friend.name-');
2599+
});
2600+
25882601
cy.viewport(600, 1024);
25892602
cy.wait(200);
25902603
cy.contains('Age').should('not.exist');
@@ -2644,6 +2657,15 @@ describe('AnalyticalTable', () => {
26442657
//popinDisplay: WithoutHeader
26452658
cy.findAllByText('PopinDisplay Modes:').should('not.exist');
26462659
cy.findAllByTestId('popinCell').should('exist');
2660+
2661+
// a11y: WithoutHeader skips header IDs in aria-labelledby
2662+
cy.get('[aria-rowindex="2"] [data-is-first-column="true"]')
2663+
.first()
2664+
.then(($cell) => {
2665+
const labelledby = $cell.attr('aria-labelledby');
2666+
expect(labelledby).to.contain('popin-v-popinDisplay-');
2667+
expect(labelledby).to.not.contain('popin-h-popinDisplay-');
2668+
});
26472669
});
26482670
});
26492671
} else {

packages/main/src/components/AnalyticalTable/defaults/Column/PopIn.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { AnalyticalTablePopinDisplay } from '../../../../enums/AnalyticalTablePo
33
import { FlexBoxAlignItems } from '../../../../enums/FlexBoxAlignItems.js';
44
import { FlexBoxDirection } from '../../../../enums/FlexBoxDirection.js';
55
import { FlexBoxWrap } from '../../../../enums/FlexBoxWrap.js';
6-
import { Text } from '../../../../webComponents/Text/index.js';
76
import { FlexBox } from '../../../FlexBox/index.js';
87
import type { CellInstance } from '../../types/index.js';
98
import { RenderColumnTypes } from '../../types/index.js';
@@ -15,7 +14,7 @@ export const PopIn = (instance: CellInstance) => {
1514
cell,
1615
row,
1716
internalRowHeight,
18-
webComponentsReactProperties: { classes: classNames },
17+
webComponentsReactProperties: { classes: classNames, uniqueId },
1918
} = instance;
2019

2120
return (
@@ -53,17 +52,20 @@ export const PopIn = (instance: CellInstance) => {
5352
const cell = column.Cell;
5453
if (typeof cell === 'string') {
5554
return (
56-
<Text maxLines={1} title={cell}>
55+
<span title={cell} className={classNames.textEllipsis}>
5756
{cell}
58-
</Text>
57+
</span>
5958
);
6059
}
61-
return makeRenderer({ ...instance, ...popInInstanceProps, isPopIn: true }, column)(column.Cell);
60+
return makeRenderer(
61+
{ ...instance, ...popInInstanceProps, cell: popInInstanceProps, isPopIn: true },
62+
column,
63+
)(column.Cell);
6264
}
6365
return popInInstanceProps?.value ? (
64-
<Text maxLines={1} title={popInInstanceProps.value}>
66+
<span title={popInInstanceProps.value} className={classNames.textEllipsis}>
6567
{popInInstanceProps.value}
66-
</Text>
68+
</span>
6769
) : null;
6870
};
6971
return (
@@ -75,11 +77,18 @@ export const PopIn = (instance: CellInstance) => {
7577
key={id}
7678
>
7779
{popinDisplay !== AnalyticalTablePopinDisplay.WithoutHeader && column?.Header && (
78-
<div className={classNames.popInHeader} data-component-name="AnalyticalTablePopinHeaderContainer">
80+
<div
81+
id={`${uniqueId}popin-h-${id}-${row.id}`}
82+
aria-hidden="true"
83+
className={classNames.popInHeader}
84+
data-component-name="AnalyticalTablePopinHeaderContainer"
85+
>
7986
{renderHeader()}:
8087
</div>
8188
)}
82-
<div style={{ height: internalRowHeight }}>{popInInstanceProps && renderCell()}</div>
89+
<div id={`${uniqueId}popin-v-${id}-${row.id}`} aria-hidden="true" style={{ height: internalRowHeight }}>
90+
{popInInstanceProps && renderCell()}
91+
</div>
8392
</FlexBox>
8493
);
8594
})}

packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.stories.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ const kitchenSinkArgs: AnalyticalTablePropTypes = {
122122
// console.log('This is your row data', row.original);
123123
return (
124124
<FlexBox>
125-
<Button icon="edit" disabled={disabled} accessibleName="Edit" />
126-
<Button icon="delete" disabled={disabled} accessibleName="Delete" />
125+
<Button icon="edit" disabled={disabled} accessibleName="Edit" tooltip="Edit" />
126+
<Button icon="delete" disabled={disabled} accessibleName="Delete" tooltip="Delete" />
127127
</FlexBox>
128128
);
129129
},
@@ -137,7 +137,7 @@ const kitchenSinkArgs: AnalyticalTablePropTypes = {
137137
columnOrder: ['friend.name', 'friend.age', 'name'],
138138
extension: (
139139
<FlexBox justifyContent={FlexBoxJustifyContent.End}>
140-
<Button icon="edit" accessibleName="edit" design="Transparent" />
140+
<Button icon="edit" accessibleName="edit" design="Transparent" tooltip="Edit" />
141141
</FlexBox>
142142
),
143143
groupable: true,
@@ -344,8 +344,8 @@ export const ResponsiveColumnsPopIn: Story = {
344344
Cell: (instance) => {
345345
return (
346346
<FlexBox>
347-
<Button icon="edit" />
348-
<Button icon="delete" />
347+
<Button icon="edit" accessibleName="Edit" tooltip="Edit" />
348+
<Button icon="delete" accessibleName="Delete" tooltip="Delete" />
349349
</FlexBox>
350350
);
351351
},

packages/main/src/components/AnalyticalTable/hooks/useA11y.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { KeyboardEventHandler } from 'react';
2+
import { AnalyticalTablePopinDisplay } from '../../../enums/AnalyticalTablePopinDisplay.js';
23
import { AnalyticalTableSelectionBehavior } from '../../../enums/AnalyticalTableSelectionBehavior.js';
34
import { AnalyticalTableSelectionMode } from '../../../enums/AnalyticalTableSelectionMode.js';
45
import type { ReactTableHooks, TableInstance } from '../types/index.js';
@@ -47,6 +48,22 @@ const setCellProps = (cellProps, { cell, instance }: { cell: TableInstance['cell
4748
const isFirstUserCol = userCols[0]?.id === column.id || userCols[0]?.accessor === column.accessor;
4849
updatedCellProps['data-is-first-column'] = isFirstUserCol;
4950

51+
const { popInColumns } = instance.state;
52+
if (isFirstUserCol && popInColumns?.length && !row.isGrouped) {
53+
if (canUseVoiceOver) {
54+
// For VoiceOver: put column header before value for context when pop-in content follows
55+
updatedCellProps['aria-labelledby'] = `${uniqueId}${column.id} ${uniqueId}${column.id}${row.id}`;
56+
}
57+
let popInLabelledBy = '';
58+
for (const popInCol of popInColumns) {
59+
if (popInCol.popinDisplay !== AnalyticalTablePopinDisplay.WithoutHeader && popInCol.column?.Header) {
60+
popInLabelledBy += ` ${uniqueId}popin-h-${popInCol.id}-${row.id}`;
61+
}
62+
popInLabelledBy += ` ${uniqueId}popin-v-${popInCol.id}-${row.id}`;
63+
}
64+
updatedCellProps['aria-labelledby'] += popInLabelledBy;
65+
}
66+
5067
if ((isFirstUserCol && rowIsExpandable) || (row.isGrouped && row.canExpand)) {
5168
updatedCellProps.onKeyDown = row.getToggleRowExpandedProps?.()?.onKeyDown;
5269
if (row.isExpanded) {

packages/main/src/components/AnalyticalTable/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ export interface AnalyticalTableColumnDefinition {
507507
* __Note:__ Use this property if there is no textual content available through the dataset (e.g. no `accessor` field available), or if you want to provide additional context when navigating to the respective cell for screen readers.
508508
*
509509
* __Note:__ To retrieve the internal `aria-label`, utilize the `cell.cellLabel` property.
510+
*
511+
* __Note:__ When defined on the first column, this overrides automatic pop-in accessibility announcements.
510512
*/
511513
cellLabel?: (param: CellLabelParam) => string;
512514
/**

0 commit comments

Comments
 (0)