Skip to content

Commit e70ffa6

Browse files
authored
feat(ui5-form, ui5-form-group): add headerLevel property (#11372)
- headerLevel API (similar to Panel and others) for customizing the aria-levels of Form and FormGroup headings according to the use-case with default values of 2 for the Form and 3 for the FormGroup. - accessibleName API to allow defining a unique accessible name of the Form, especially when used without a header or when multiple forms are used (as the aria-label should be unique and even if we set built-in one, the tools would complain that the role+accessible name combination isn't unique). - updates font-size of the FormGroup heading (from H5 to H6) to to match the latest design Fixes: #11371
1 parent 64473e3 commit e70ffa6

File tree

5 files changed

+133
-3
lines changed

5 files changed

+133
-3
lines changed

packages/main/cypress/specs/Form.cy.tsx

+81-1
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,8 @@ describe("Accessibility", () => {
777777
cy.get("@form")
778778
.shadow()
779779
.find(".ui5-form-root")
780-
.should("have.attr", "role", "form");
780+
.should("have.attr", "role", "form")
781+
.and("not.have.attr", "aria-label", "Form");
781782

782783
cy.get("@form")
783784
.should("have.attr", "data-sap-ui-fastnavgroup", "true");
@@ -793,6 +794,57 @@ describe("Accessibility", () => {
793794
});
794795
});
795796

797+
it("tests 'role' and 'aria-label' of form without header", () => {
798+
cy.mount(<Form>
799+
<FormItem>
800+
<Label>Name:</Label>
801+
<Text>Red Point Stores</Text>
802+
</FormItem>
803+
<FormItem>
804+
<Label>Twitter:</Label>
805+
<Text>@sap</Text>
806+
</FormItem>
807+
<FormItem>
808+
<Label>Name:</Label>
809+
<Text>Red Point Stores</Text>
810+
</FormItem>
811+
</Form>);
812+
813+
cy.get("[ui5-form]")
814+
.as("form");
815+
816+
cy.get("@form")
817+
.shadow()
818+
.find(".ui5-form-root")
819+
.should("have.attr", "role", "form")
820+
.and("have.attr", "aria-label", "Form");
821+
});
822+
823+
it("tests 'aria-label' via 'accessibleName'", () => {
824+
cy.mount(<Form headerText="Form header text" accessibleName="basic form">
825+
<FormItem>
826+
<Label>Name:</Label>
827+
<Text>Red Point Stores</Text>
828+
</FormItem>
829+
<FormItem>
830+
<Label>Twitter:</Label>
831+
<Text>@sap</Text>
832+
</FormItem>
833+
<FormItem>
834+
<Label>Name:</Label>
835+
<Text>Red Point Stores</Text>
836+
</FormItem>
837+
</Form>);
838+
839+
cy.get("[ui5-form]")
840+
.as("form");
841+
842+
cy.get("@form")
843+
.shadow()
844+
.find(".ui5-form-root")
845+
.should("have.attr", "aria-label", "basic form");
846+
});
847+
796848
it("tests F6 navigation", () => {
797849
cy.mount(
798850
<>
@@ -937,6 +989,34 @@ ui5AccDescribe("Automated accessibility tests", () => {
937989
</Form>
938990
);
939991

992+
cy.ui5CheckA11y();
993+
});
994+
995+
it("without header", () => {
996+
cy.mount(
997+
<Form>
998+
<FormItem>
999+
<Label slot="labelContent">Name:</Label>
1000+
<Text>Red Point Stores</Text>
1001+
</FormItem>
1002+
1003+
<FormItem>
1004+
<Label slot="labelContent">ZIP Code/City:</Label>
1005+
<Text>411 Maintown</Text>
1006+
</FormItem>
1007+
1008+
<FormItem>
1009+
<Label slot="labelContent">Street:</Label>
1010+
<Text>Main St 1618</Text>
1011+
</FormItem>
1012+
1013+
<FormItem>
1014+
<Label slot="labelContent">Country:</Label>
1015+
<Text>Germany</Text>
1016+
</FormItem>
1017+
</Form>
1018+
);
1019+
9401020
cy.ui5CheckA11y();
9411021
})
9421022
});

packages/main/src/Form.ts

+35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
33
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
4+
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
45
import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js";
56
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
67
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
@@ -12,6 +13,10 @@ import FormCss from "./generated/themes/Form.css.js";
1213

1314
import type FormItemSpacing from "./types/FormItemSpacing.js";
1415
import type FormGroup from "./FormGroup.js";
16+
import type TitleLevel from "./types/TitleLevel.js";
17+
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
18+
19+
import { FORM_ACCESSIBLE_NAME } from "./generated/i18n/i18n-defaults.js";
1520

1621
const additionalStylesMap = new Map<string, string>();
1722

@@ -41,6 +46,7 @@ interface IFormItem extends UI5Element {
4146
colsS?: number;
4247
columnSpan?: number;
4348
headerText?: string;
49+
headerLevel?: `${TitleLevel}`;
4450
}
4551

4652
type GroupItemsInfo = {
@@ -206,6 +212,15 @@ type ItemsInfo = {
206212
template: FormTemplate,
207213
})
208214
class Form extends UI5Element {
215+
/**
216+
* Defines the accessible ARIA name of the component.
217+
* @default undefined
218+
* @public
219+
* @since 2.10.0
220+
*/
221+
@property()
222+
accessibleName?: string;
223+
209224
/**
210225
* Defines the number of columns to distribute the form content by breakpoint.
211226
*
@@ -265,6 +280,16 @@ class Form extends UI5Element {
265280
@property()
266281
headerText?: string;
267282

283+
/**
284+
* Defines the compoennt heading level,
285+
* set by the `headerText`.
286+
* @default "H2"
287+
* @since 2.10.0
288+
* @public
289+
*/
290+
@property()
291+
headerLevel: `${TitleLevel}` = "H2";
292+
268293
/**
269294
* Defines the vertical spacing between form items.
270295
*
@@ -302,6 +327,9 @@ class Form extends UI5Element {
302327
})
303328
items!: Array<IFormItem>;
304329

330+
@i18n("@ui5/webcomponents")
331+
static i18nBundle: I18nBundle;
332+
305333
/**
306334
* @private
307335
*/
@@ -514,6 +542,13 @@ class Form extends UI5Element {
514542
return !!this.header.length;
515543
}
516544

545+
get effectiveAccessibleName() {
546+
if (this.accessibleName) {
547+
return this.accessibleName;
548+
}
549+
return this.hasHeader ? undefined : Form.i18nBundle.getText(FORM_ACCESSIBLE_NAME);
550+
}
551+
517552
get effectiveАccessibleNameRef(): string | undefined {
518553
return this.hasHeaderText && !this.hasCustomHeader ? `${this._id}-header-text` : undefined;
519554
}

packages/main/src/FormGroup.ts

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
66
import type FormItem from "./FormItem.js";
77
import type { IFormItem } from "./Form.js";
88
import type FormItemSpacing from "./types/FormItemSpacing.js";
9+
import type TitleLevel from "./types/TitleLevel.js";
910

1011
/**
1112
* @class
@@ -47,6 +48,16 @@ class FormGroup extends UI5Element implements IFormItem {
4748
@property()
4849
headerText?: string;
4950

51+
/**
52+
* Defines the compoennt heading level,
53+
* set by the `headerText`.
54+
* @default "H3"
55+
* @public
56+
* @since 2.10.0
57+
*/
58+
@property()
59+
headerLevel: `${TitleLevel}` = "H3";
60+
5061
/**
5162
* Defines column span of the component,
5263
* e.g how many columns the group should span to.

packages/main/src/FormTemplate.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ export default function FormTemplate(this: Form) {
66
<div
77
class="ui5-form-root"
88
role={this.effectiveAccessibleRole}
9+
aria-label={this.effectiveAccessibleName}
910
aria-labelledby={this.effectiveАccessibleNameRef}
1011
>
1112
{this.hasHeader &&
1213
<div class="ui5-form-header" part="header">
1314
{this.hasCustomHeader ?
1415
<slot name="header"></slot>
1516
:
16-
<Title id={`${this._id}-header-text`} level="H4">{this.headerText}</Title>
17+
<Title id={`${this._id}-header-text`} level={this.headerLevel}>{this.headerText}</Title>
1718
}
1819
</div>
1920
}
@@ -38,7 +39,7 @@ export default function FormTemplate(this: Form) {
3839
<div class="ui5-form-group" role="form" aria-labelledby={groupItemInfo.accessibleNameRef}>
3940
{groupItem.headerText &&
4041
<div class="ui5-form-group-heading">
41-
<Title id={`${groupItem._id}-group-header-text`} level="H6">{groupItem.headerText}</Title>
42+
<Title id={`${groupItem._id}-group-header-text`} level={groupItem.headerLevel} size="H6">{groupItem.headerText}</Title>
4243
</div>
4344
}
4445

packages/main/src/i18n/messagebundle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@ TOOLBAR_OVERFLOW_BUTTON_ARIA_LABEL=Additional Options
614614
#XACT: ARIA announcement for Toolbar's popup
615615
TOOLBAR_POPOVER_AVAILABLE_VALUES=Available Values
616616

617+
#XACT: ARIA announcement for the Form aria-label attribute
618+
FORM_ACCESSIBLE_NAME=Form
619+
617620
#XMSG: Text used for reporting that a radio button group requires one of the radio buttons to be checked
618621
FORM_CHECKABLE_REQUIRED=Please tick this box if you want to proceed.
619622

0 commit comments

Comments
 (0)