Skip to content

Commit 02e7183

Browse files
authored
Merge 74fdf3b into 1ebf138
2 parents 1ebf138 + 74fdf3b commit 02e7183

File tree

13 files changed

+510
-307
lines changed

13 files changed

+510
-307
lines changed

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

Lines changed: 173 additions & 99 deletions
Large diffs are not rendered by default.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const kitchenSinkArgs: AnalyticalTablePropTypes = {
6666
},
6767
{
6868
Header: () => <span>Friend Age</span>,
69-
headerLabel: 'Friend Age',
69+
headerLabel: 'Custom Header Label',
7070
accessor: 'friend.age',
7171
autoResizable: true,
7272
hAlign: TextAlign.End,
@@ -130,7 +130,7 @@ const kitchenSinkArgs: AnalyticalTablePropTypes = {
130130
return `${cell.cellLabel} press TAB to focus active elements inside this cell`;
131131
},
132132
},
133-
],
133+
].slice(0, 2),
134134
filterable: true,
135135
alternateRowColor: true,
136136
columnOrder: ['friend.name', 'friend.age', 'name'],
@@ -195,6 +195,7 @@ const meta = {
195195
visibleRows: 5,
196196
// sb actions has a huge impact on performance here.
197197
onTableScroll: undefined,
198+
header: 'TableTitle',
198199
},
199200
argTypes: {
200201
data: { control: { disable: true } },

packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
200200
borderInlineStart: dragOver ? `3px solid ${ThemingParameters.sapSelectedColor}` : undefined,
201201
}}
202202
aria-haspopup={hasPopover ? 'menu' : undefined}
203+
aria-expanded={hasPopover ? (popoverOpen ? 'true' : 'false') : undefined}
204+
aria-controls={hasPopover ? `${id}-popover` : undefined}
203205
role={role}
204206
draggable={isDraggable}
205207
onDragEnter={onDragEnter}
@@ -278,6 +280,7 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
278280
// render the popover and add the props to the table instance
279281
column.render(RenderColumnTypes.Popover, {
280282
popoverProps: {
283+
id: `${id}-popover`,
281284
openerRef: columnHeaderRef,
282285
setOpen: setPopoverOpen,
283286
},

packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
167167
}
168168
prepareRow(row);
169169
const { key, ...rowProps } = row.getRowProps({
170-
'aria-rowindex': virtualRow.index + 1,
170+
'aria-rowindex': virtualRow.index + 2,
171171
'data-virtual-row-index': virtualRow.index,
172172
});
173173
const isNavigatedCell = typeof markNavigatedRow === 'function' ? markNavigatedRow(row) : false;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ export const Cell = (props: CellInstance) => {
1111
if (isGrouped) {
1212
cellContent += ` (${row.subRows.length})`;
1313
}
14+
1415
return (
1516
<span
17+
id={`${webComponentsReactProperties.uniqueId}${column.id}${row.id}`}
1618
title={cellContent}
1719
className={webComponentsReactProperties.classes.tableText}
1820
data-column-id-cell-text={column.id}
21+
// VoiceOver announces blank because of `aria-hidden` although `aria-labelledby` is set on the `gridcell` element - this is a known bug and there's no workaround
22+
aria-hidden="true"
1923
>
2024
{cellContent}
2125
</span>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import type { TableInstanceWithPopoverProps } from '../../types/index.js';
3434
import { RenderColumnTypes } from '../../types/index.js';
3535

3636
export const ColumnHeaderModal = (instance: TableInstanceWithPopoverProps) => {
37-
const { setOpen, openerRef } = instance.popoverProps;
37+
const { setOpen, openerRef, id } = instance.popoverProps;
3838
const { column, state, webComponentsReactProperties } = instance;
3939
const { isRtl, groupBy } = state;
4040
const { onGroup, onSort, classes: classNames } = webComponentsReactProperties;
@@ -174,10 +174,11 @@ export const ColumnHeaderModal = (instance: TableInstanceWithPopoverProps) => {
174174
ref.current.open = true;
175175
});
176176
}
177-
}, []);
177+
}, [openerRef]);
178178

179179
return (
180180
<Popover
181+
id={id}
181182
hideArrow
182183
horizontalAlign={horizontalAlign}
183184
placement={PopoverPlacement.Bottom}

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

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,34 @@ import type { ReactTableHooks, TableInstance } from '../types/index.js';
55

66
interface UpdatedCellProptypes {
77
onKeyDown?: KeyboardEventHandler<HTMLDivElement>;
8-
'aria-expanded'?: string | boolean;
8+
'aria-expanded'?: string;
99
'aria-label'?: string;
10-
'aria-colindex'?: number;
11-
role?: string;
10+
'aria-colindex': number;
11+
'aria-describedby'?: string;
12+
'aria-labelledby': string;
13+
role: string;
1214
}
1315

1416
const setCellProps = (cellProps, { cell, instance }: { cell: TableInstance['cell']; instance: TableInstance }) => {
1517
const { column, row, value } = cell;
1618
const columnIndex = instance.visibleColumns.findIndex(({ id }) => id === column.id);
17-
const { alwaysShowSubComponent, renderRowSubComponent, translatableTexts, selectionMode, selectionBehavior } =
18-
instance.webComponentsReactProperties;
19-
const updatedCellProps: UpdatedCellProptypes = { 'aria-colindex': columnIndex + 1, role: 'gridcell' }; // aria index is 1 based, not 0
19+
const {
20+
alwaysShowSubComponent,
21+
renderRowSubComponent,
22+
selectionMode,
23+
selectionBehavior,
24+
a11yElementIds,
25+
uniqueId,
26+
canUseVoiceOver,
27+
} = instance.webComponentsReactProperties;
28+
29+
const updatedCellProps: UpdatedCellProptypes = {
30+
// aria index is 1 based, not 0
31+
'aria-colindex': columnIndex + 1,
32+
role: 'gridcell',
33+
// header label
34+
'aria-labelledby': `${uniqueId}${column.id}${row.id}` + (canUseVoiceOver ? ` ${uniqueId}${column.id}` : ''),
35+
};
2036

2137
const RowSubComponent = typeof renderRowSubComponent === 'function' ? renderRowSubComponent(row) : undefined;
2238
const rowIsExpandable = row.canExpand || (RowSubComponent && !alwaysShowSubComponent);
@@ -30,24 +46,16 @@ const setCellProps = (cellProps, { cell, instance }: { cell: TableInstance['cell
3046

3147
const isFirstUserCol = userCols[0]?.id === column.id || userCols[0]?.accessor === column.accessor;
3248
updatedCellProps['data-is-first-column'] = isFirstUserCol;
33-
updatedCellProps['aria-label'] = column.headerLabel || (typeof column.Header === 'string' ? column.Header : '');
34-
updatedCellProps['aria-label'] &&= `${updatedCellProps['aria-label']} `;
35-
updatedCellProps['aria-label'] += value || value === 0 ? `${value} ` : '';
3649

3750
if ((isFirstUserCol && rowIsExpandable) || (row.isGrouped && row.canExpand)) {
3851
updatedCellProps.onKeyDown = row.getToggleRowExpandedProps?.()?.onKeyDown;
39-
let ariaLabel = '';
40-
if (row.isGrouped) {
41-
ariaLabel += translatableTexts.groupedA11yText + ',';
42-
}
4352
if (row.isExpanded) {
4453
updatedCellProps['aria-expanded'] = 'true';
45-
ariaLabel += ` ${translatableTexts.collapseA11yText}`;
54+
updatedCellProps['aria-describedby'] = ' ' + a11yElementIds.cellCollapseDescId;
4655
} else {
4756
updatedCellProps['aria-expanded'] = 'false';
48-
ariaLabel += ` ${translatableTexts.expandA11yText}`;
57+
updatedCellProps['aria-describedby'] = ' ' + a11yElementIds.cellExpandDescId;
4958
}
50-
updatedCellProps['aria-label'] += ariaLabel;
5159
} else if (
5260
(selectionMode !== AnalyticalTableSelectionMode.None &&
5361
selectionBehavior !== AnalyticalTableSelectionBehavior.RowSelector &&
@@ -56,17 +64,21 @@ const setCellProps = (cellProps, { cell, instance }: { cell: TableInstance['cell
5664
) {
5765
if (row.isSelected) {
5866
updatedCellProps['aria-selected'] = 'true';
59-
updatedCellProps['aria-label'] += ` ${translatableTexts.unselectA11yText}`;
67+
updatedCellProps['aria-describedby'] = ' ' + a11yElementIds.cellUnselectDescId;
6068
} else {
6169
updatedCellProps['aria-selected'] = 'false';
62-
updatedCellProps['aria-label'] += ` ${translatableTexts.selectA11yText}`;
70+
updatedCellProps['aria-describedby'] = ' ' + a11yElementIds.cellSelectDescId;
6371
}
6472
}
6573
const { cellLabel } = cell.column;
6674
if (typeof cellLabel === 'function') {
67-
cell.cellLabel = updatedCellProps['aria-label'];
75+
const cellHeaderLabel = column.headerLabel || (typeof column.Header === 'string' ? column.Header : '');
76+
const cellValueLabel = value || value === 0 ? `${value} ` : '';
77+
cell.cellLabel = `${cellHeaderLabel} ${cellValueLabel}`;
6878
updatedCellProps['aria-label'] = cellLabel({ cell, instance });
79+
updatedCellProps['aria-labelledby'] = undefined;
6980
}
81+
7082
return [cellProps, updatedCellProps];
7183
};
7284

@@ -108,11 +120,30 @@ const setHeaderProps = (
108120
: translatableTexts.selectAllA11yText;
109121
}
110122

123+
if (column.id === '__ui5wcr__internal_selection_column') {
124+
updatedProps['aria-label'] += ' ' + translatableTexts.selectionHeaderCellText;
125+
}
126+
127+
if (column.id === '__ui5wcr__internal_highlight_column') {
128+
updatedProps['aria-label'] += ' ' + translatableTexts.highlightHeaderCellText;
129+
}
130+
131+
if (column.id === '__ui5wcr__internal_navigation_column') {
132+
updatedProps['aria-label'] += ' ' + translatableTexts.navigationHeaderCellText;
133+
}
134+
135+
updatedProps['aria-label'] ||= undefined;
136+
111137
return [headerProps, { isFiltered, ...updatedProps }];
112138
};
113139

140+
const setHeaderGroupProps = (props) => {
141+
return [props, { 'aria-rowindex': 1 }];
142+
};
143+
114144
export const useA11y = (hooks: ReactTableHooks) => {
115145
hooks.getCellProps.push(setCellProps);
116146
hooks.getHeaderProps.push(setHeaderProps);
147+
hooks.getHeaderGroupProps.push(setHeaderGroupProps);
117148
};
118149
useA11y.pluginName = 'useA11y';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { isIOS, isMac } from '@ui5/webcomponents-react-base/Device';
2+
import { useEffect, useState } from 'react';
3+
4+
/**
5+
* SSR ready check for macOS or iOS (Apple VoiceOver support).
6+
*/
7+
export function useCanUseVoiceOver() {
8+
const [canUseVoiceOver, setCanUseVoiceOver] = useState(false);
9+
10+
useEffect(() => {
11+
setCanUseVoiceOver(isIOS() || isMac());
12+
}, []);
13+
14+
return canUseVoiceOver;
15+
}

0 commit comments

Comments
 (0)