Skip to content

Commit f414c18

Browse files
authored
Merge branch 'main' into chore/release-please-migration
2 parents c16e72c + 2fe532c commit f414c18

9 files changed

Lines changed: 131 additions & 23 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
"@ui5/webcomponents-compat": "2.22.0",
5353
"@ui5/webcomponents-fiori": "2.22.0",
5454
"@ui5/webcomponents-icons": "2.22.0",
55-
"react": "19.2.6",
56-
"react-dom": "19.2.6",
55+
"react": "19.2.7",
56+
"react-dom": "19.2.7",
5757
"remark-gfm": "4.0.1",
5858
"storybook": "10.4.1",
5959
"tocbot": "4.36.8"

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3253,6 +3253,35 @@ describe('AnalyticalTable', () => {
32533253
);
32543254
});
32553255

3256+
it('a11y: accessibleName and accessibleNameRef', () => {
3257+
// no aria-labelledby
3258+
cy.mount(<AnalyticalTable columns={columns} data={data} />);
3259+
cy.get('[data-component-name="AnalyticalTableContainer"]').should('not.have.attr', 'aria-labelledby');
3260+
cy.get('[data-component-name="AnalyticalTableContainer"]').should('not.have.attr', 'aria-label');
3261+
3262+
// with header: aria-labelledby points to the title bar
3263+
cy.mount(<AnalyticalTable columns={columns} data={data} header="Items Table" />);
3264+
cy.get('[data-component-name="AnalyticalTableContainer"]')
3265+
.should('have.attr', 'aria-labelledby')
3266+
.then((labelledby) => {
3267+
cy.get(`[id="${labelledby}"]`).should('exist');
3268+
});
3269+
3270+
// accessibleName: aria-label on the grid and removes the header connection
3271+
cy.mount(<AnalyticalTable columns={columns} data={data} header="Items Table" accessibleName="Financing Details" />);
3272+
cy.get('[data-component-name="AnalyticalTableContainer"]').should('have.attr', 'aria-label', 'Financing Details');
3273+
cy.get('[data-component-name="AnalyticalTableContainer"]').should('not.have.attr', 'aria-labelledby');
3274+
3275+
// accessibleNameRef: overrides the header connection
3276+
cy.mount(
3277+
<>
3278+
<span id="custom-label">Custom Table Label</span>
3279+
<AnalyticalTable columns={columns} data={data} header="Items Table" accessibleNameRef="custom-label" />
3280+
</>,
3281+
);
3282+
cy.get('[data-component-name="AnalyticalTableContainer"]').should('have.attr', 'aria-labelledby', 'custom-label');
3283+
});
3284+
32563285
it("Expandable: don't scroll when expanded/collapsed", () => {
32573286
const TestComp = () => {
32583287
const tableInstanceRef = useRef<{ toggleRowExpanded?: (e: string) => void }>({});

packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,22 @@ function ContextMenuExample() {
549549

550550
</details>
551551

552+
## Accessibility
553+
554+
This example demonstrates possible accessibility configuration for the `AnalyticalTable`:
555+
556+
- **`accessibleName`**: Sets a concise `aria-label` on the table grid, giving screen readers a meaningful table name.
557+
- **`accessibleNameRef`**: References the ID of an external labelling element via `aria-labelledby`. When either `accessibleName` or `accessibleNameRef` is set, the automatic connection to the `header` prop is removed.
558+
- **`headerLabel`** (column option): Provides a screen-reader-accessible label for column headers that have no textual content.
559+
- **`cellLabel`** (column option): Returns a descriptive `aria-label` for cells whose visual content is not self-explanatory.
560+
561+
**Note:**
562+
563+
- The example also includes the [useAnnounceEmptyCells](?path=/docs/data-display-analyticaltable-plugin-hooks--docs#announce-empty-cells) plugin hook, which adds explicit empty-cell announcements for screen readers that do not detect them on their own. As this could lead to duplicate screen reader announcement, use with caution.
564+
- When the `header` prop is set, its text is automatically used as the table's accessible name. To provide a different accessible name, use `accessibleName` or `accessibleNameRef` instead.
565+
566+
<Canvas of={ComponentStories.Accessibility}/>
567+
552568
## Kitchen Sink
553569

554570
A comprehensive example combining many AnalyticalTable features: sorting, filtering, grouping, custom cells, row and navigation highlighting, infinite scrolling, column reordering, vertical alignment, `scaleWidthModeOptions` for custom renderers, `retainColumnWidth`, `sortDescFirst`, and more.

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { FlexBox } from '../../FlexBox/index.js';
3838
import { ObjectStatus } from '../../ObjectStatus/index.js';
3939
import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from '../index.js';
4040
import { AnalyticalTable } from '../index.js';
41+
import { useAnnounceEmptyCells } from '../pluginHooks/AnalyticalTableHooks.js';
4142

4243
const kitchenSinkArgs: AnalyticalTablePropTypes = {
4344
data: dataLarge,
@@ -680,6 +681,53 @@ export const ContextMenu: Story = {
680681
},
681682
};
682683

684+
export const Accessibility: Story = {
685+
render() {
686+
const tableHooks = useMemo(() => [useAnnounceEmptyCells], []);
687+
const columns = useMemo<AnalyticalTableColumnDefinition[]>(
688+
() => [
689+
{
690+
Header: 'Name',
691+
accessor: 'name',
692+
},
693+
{
694+
Header: 'Age',
695+
accessor: 'age',
696+
hAlign: 'End',
697+
},
698+
{
699+
Header: '',
700+
headerLabel: 'Actions',
701+
id: 'actions',
702+
disableFilters: true,
703+
disableSortBy: true,
704+
disableGroupBy: true,
705+
disableDragAndDrop: true,
706+
Cell: () => (
707+
<FlexBox>
708+
<Button icon={editIcon} accessibleName="Edit" tooltip="Edit" />
709+
<Button icon={deleteIcon} accessibleName="Delete" tooltip="Delete" />
710+
</FlexBox>
711+
),
712+
cellLabel: () => 'Actions: Edit, Delete',
713+
},
714+
],
715+
[],
716+
);
717+
const data = useMemo(() => [{ name: undefined, age: 80 }, ...dataLarge.slice(0, 4)], []);
718+
return (
719+
<AnalyticalTable
720+
columns={columns}
721+
data={data}
722+
header="Friends"
723+
accessibleName="Friends Table"
724+
selectionMode={AnalyticalTableSelectionMode.Multiple}
725+
tableHooks={tableHooks}
726+
/>
727+
);
728+
},
729+
};
730+
683731
export const KitchenSink: Story = {
684732
args: kitchenSinkArgs,
685733
};

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ const measureElement = (el: HTMLElement) => {
134134
*/
135135
const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTypes>((props, ref) => {
136136
const {
137+
accessibleName,
138+
accessibleNameRef,
137139
adjustTableHeightOnPopIn,
138140
alternateRowColor,
139141
alwaysShowBusyIndicator,
@@ -794,15 +796,16 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
794796
</span>
795797
<div
796798
tabIndex={0}
797-
aria-labelledby={`${titleBarId} ${invalidTableTextId}`}
799+
aria-labelledby={`${accessibleNameRef ?? titleBarId} ${invalidTableTextId}`}
798800
role="region"
799801
data-component-name="AnalyticalTableOverlay"
800802
className={classNames.overlay}
801803
/>
802804
</>
803805
)}
804806
<div
805-
aria-labelledby={titleBarId}
807+
aria-label={accessibleName}
808+
aria-labelledby={accessibleNameRef ?? (header && !accessibleName ? titleBarId : undefined)}
806809
{...getTableProps()}
807810
tabIndex={loading || showOverlay ? -1 : 0}
808811
role={isTreeTable ? 'treegrid' : 'grid'}

packages/main/src/components/AnalyticalTable/react-table/publicUtils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function mergeProps(...propList: Record<string, any>[]): Record<string, any> {
2727
};
2828

2929
if (style) {
30-
props.style = props.style ? { ...(props.style || {}), ...(style || {}) } : style;
30+
props.style = props.style ? { ...props.style, ...style } : style;
3131
}
3232

3333
if (className) {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,9 +766,21 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
766766
/**
767767
* Component or text rendered in the header section of the `AnalyticalTable`.
768768
*
769-
* __Note:__ If not set, it will be hidden.
769+
* __Note:__ If not set, the header section is not rendered. When set, its text is automatically used as the table's accessible name. To provide a different accessible name, use `accessibleName` or `accessibleNameRef` instead.
770770
*/
771771
header?: ReactNode;
772+
/**
773+
* Defines the accessible name of the table.
774+
*
775+
* __Note:__ If set, the `aria-labelledby` derived from the `header` prop will not be applied to the table grid.
776+
*/
777+
accessibleName?: string;
778+
/**
779+
* Defines the IDs of the elements that label the table.
780+
*
781+
* __Note:__ If set, the `aria-labelledby` derived from the `header` prop will not be applied to the table grid.
782+
*/
783+
accessibleNameRef?: string;
772784
/**
773785
* Extension section of the Table. If not set, no extension area will be rendered
774786
*/

packages/mcp-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@
5353
"@ui5/webcomponents-react": "workspace:~",
5454
"ava": "8.0.1",
5555
"react-docgen-typescript": "2.4.0",
56-
"tsx": "4.22.3"
56+
"tsx": "4.22.4"
5757
}
5858
}

yarn.lock

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5987,7 +5987,7 @@ __metadata:
59875987
"@ui5/webcomponents-react": "workspace:~"
59885988
ava: "npm:8.0.1"
59895989
react-docgen-typescript: "npm:2.4.0"
5990-
tsx: "npm:4.22.3"
5990+
tsx: "npm:4.22.4"
59915991
zod: "npm:4.4.3"
59925992
bin:
59935993
ui5-wcr-mcp: ./dist/index.js
@@ -19534,14 +19534,14 @@ __metadata:
1953419534
languageName: node
1953519535
linkType: hard
1953619536

19537-
"react-dom@npm:19.2.6, react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
19538-
version: 19.2.6
19539-
resolution: "react-dom@npm:19.2.6"
19537+
"react-dom@npm:19.2.7, react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
19538+
version: 19.2.7
19539+
resolution: "react-dom@npm:19.2.7"
1954019540
dependencies:
1954119541
scheduler: "npm:^0.27.0"
1954219542
peerDependencies:
19543-
react: ^19.2.6
19544-
checksum: 10c0/dbf2aef67857c03a612bc9316a4562e5066f43fa04bf28f7f0734963fc1350da2c9f8eb973930655453281079f30cc8222bab383dbec4b5097084e2c081e3334
19543+
react: ^19.2.7
19544+
checksum: 10c0/970ff600f6e80d47d39e2f226f12f226173b3cba3382efc97c5f0cd663de9af38c7a4c11c213fb936094faeac83060d660247accaa96b752180d5b951b9cfecb
1954519545
languageName: node
1954619546
linkType: hard
1954719547

@@ -19595,10 +19595,10 @@ __metadata:
1959519595
languageName: node
1959619596
linkType: hard
1959719597

19598-
"react@npm:19.2.6, react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
19599-
version: 19.2.6
19600-
resolution: "react@npm:19.2.6"
19601-
checksum: 10c0/66afde33b9a9ee87b1e1cae39d8e7e040d1262e719524fd70660c4d4ce79929c532ac19fc3df5a911edaf02768fdf2c49de4ede1ba99bc6aad72796e0e26e798
19598+
"react@npm:19.2.7, react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
19599+
version: 19.2.7
19600+
resolution: "react@npm:19.2.7"
19601+
checksum: 10c0/0bd0e2f1bbd4ba97561c6597bf8a5fec05e6476fe61e165c1065598d16668efc6715205599c94d3ddd49d36cb0f21cbf1b9bcc18ee840b805ce222c3e8d558ac
1960219602
languageName: node
1960319603
linkType: hard
1960419604

@@ -22007,9 +22007,9 @@ __metadata:
2200722007
languageName: node
2200822008
linkType: hard
2200922009

22010-
"tsx@npm:4.22.3":
22011-
version: 4.22.3
22012-
resolution: "tsx@npm:4.22.3"
22010+
"tsx@npm:4.22.4":
22011+
version: 4.22.4
22012+
resolution: "tsx@npm:4.22.4"
2201322013
dependencies:
2201422014
esbuild: "npm:~0.28.0"
2201522015
fsevents: "npm:~2.3.3"
@@ -22018,7 +22018,7 @@ __metadata:
2201822018
optional: true
2201922019
bin:
2202022020
tsx: dist/cli.mjs
22021-
checksum: 10c0/09a5a7d354ff75e5af6285217d709ec80a46c2ee19e0d9b5c5d00eab81047c3ea6b2a5498a2c5255f3960d6e7acd1a1eee34b9433dc56fb32907ca3b38271f41
22021+
checksum: 10c0/3df31eb4929ff501b40b122163705b201ea57a492581e14312ae95d21eb015b33ded46a3fd564f9c89a0e8083186987fc4f10dd38182c4ad6241605974f6c927
2202222022
languageName: node
2202322023
linkType: hard
2202422024

@@ -22344,8 +22344,8 @@ __metadata:
2234422344
postcss-modules: "npm:6.0.1"
2234522345
prettier: "npm:3.8.3"
2234622346
publint: "npm:0.3.21"
22347-
react: "npm:19.2.6"
22348-
react-dom: "npm:19.2.6"
22347+
react: "npm:19.2.7"
22348+
react-dom: "npm:19.2.7"
2234922349
remark-gfm: "npm:4.0.1"
2235022350
rimraf: "npm:6.1.3"
2235122351
storybook: "npm:10.4.1"

0 commit comments

Comments
 (0)