Skip to content

Commit 16568c2

Browse files
myordanovelenastoyanovaa
authored andcommitted
feat: Improve accessibility of components (#613)
1 parent 9bd8ca7 commit 16568c2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+356
-60
lines changed

packages/main/lib/i18n-transform/USED_KEYS.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ TEXTAREA_CHARACTERS_LEFT
22
TEXTAREA_CHARACTERS_EXCEEDED
33
PANEL_ICON
44
MULTIINPUT_SHOW_MORE_TOKENS
5-
MESSAGE_STRIP_CLOSE_BUTTON
5+
MESSAGE_STRIP_CLOSE_BUTTON
6+
BUTTON_ARIA_TYPE_ACCEPT
7+
BUTTON_ARIA_TYPE_REJECT
8+
BUTTON_ARIA_TYPE_EMPHASIZED
9+
LINK_SUBTLE
10+
LINK_EMPHASIZED

packages/main/src/Badge.hbs

+2
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
{{#if hasText}}
77
<label class="ui5-badge-text"><bdi><slot></slot></bdi></label>
88
{{/if}}
9+
10+
<span class="ui5-hidden-text">{{badgeDescription}}</span>
911
</div>

packages/main/src/Badge.js

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Icon from "./Icon.js";
66
// Template
77
import BadgeTemplate from "./generated/templates/BadgeTemplate.lit.js";
88

9+
import { BADGE_DESCRIPTION } from "./i18n/defaults.js";
10+
911
// Styles
1012
import badgeCss from "./generated/themes/Badge.css.js";
1113

@@ -120,6 +122,10 @@ class Badge extends UI5Element {
120122
get rtl() {
121123
return getRTL() ? "rtl" : undefined;
122124
}
125+
126+
get badgeDescription() {
127+
return BADGE_DESCRIPTION.defaultText;
128+
}
123129
}
124130

125131
Badge.define();

packages/main/src/BusyIndicator.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="ui5-busyindicator-root">
22

33
{{#if active}}
4-
<div class="ui5-busyindicator-dynamic-content">
4+
<div class="ui5-busyindicator-dynamic-content" tabindex="0" role="progressbar" aria-valuemin="0" aria-valuemax="100" title="{{ariaTitle}}">
55
<div class="ui5-busyindicator-circle circle-animation-0"></div>
66
<div class="ui5-busyindicator-circle circle-animation-1"></div>
77
<div class="ui5-busyindicator-circle circle-animation-2"></div>

packages/main/src/BusyIndicator.js

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import BusyIndicatorSize from "./types/BusyIndicatorSize.js";
55
// Template
66
import BusyIndicatorTemplate from "./generated/templates/BusyIndicatorTemplate.lit.js";
77

8+
import { BUSY_INDICATOR_TITLE } from "./i18n/defaults.js";
9+
810
// Styles
911
import busyIndicatorCss from "./generated/themes/BusyIndicator.css.js";
1012

@@ -90,6 +92,10 @@ class BusyIndicator extends UI5Element {
9092
static get template() {
9193
return BusyIndicatorTemplate;
9294
}
95+
96+
get ariaTitle() {
97+
return BUSY_INDICATOR_TITLE.defaultText;
98+
}
9399
}
94100

95101
BusyIndicator.define();

packages/main/src/Button.hbs

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
</bdi>
2424
</span>
2525
{{/if}}
26+
27+
{{#if hasButtonType}}
28+
<span class="ui5-hidden-text">{{buttonTypeText}}</span>
29+
{{/if}}
2630
</button>
2731

2832
{{#*inline "ariaPressed"}}{{/inline}}

packages/main/src/Button.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/events/PseudoEven
44
import { getCompactSize } from "@ui5/webcomponents-base/dist/config/CompactSize.js";
55
import { getRTL } from "@ui5/webcomponents-base/dist/config/RTL.js";
66
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
7+
import { fetchResourceBundle, getResourceBundle } from "@ui5/webcomponents-base/dist/ResourceBundle.js";
78
import ButtonDesign from "./types/ButtonDesign.js";
89
import ButtonTemplate from "./generated/templates/ButtonTemplate.lit.js";
910
import Icon from "./Icon.js";
1011

12+
import { BUTTON_ARIA_TYPE_ACCEPT, BUTTON_ARIA_TYPE_REJECT, BUTTON_ARIA_TYPE_EMPHASIZED } from "./i18n/defaults.js";
13+
1114
// Styles
1215
import buttonCss from "./generated/themes/Button.css.js";
1316

@@ -190,6 +193,8 @@ class Button extends UI5Element {
190193
this._active = false;
191194
}
192195
};
196+
197+
this.resourceBundle = getResourceBundle("@ui5/webcomponents");
193198
}
194199

195200
onBeforeRendering() {
@@ -266,8 +271,27 @@ class Button extends UI5Element {
266271
return getRTL() ? "rtl" : undefined;
267272
}
268273

274+
get hasButtonType() {
275+
return this.design !== ButtonDesign.Default && this.design !== ButtonDesign.Transparent;
276+
}
277+
278+
static typeTextMappings() {
279+
return {
280+
"Positive": BUTTON_ARIA_TYPE_ACCEPT,
281+
"Negative": BUTTON_ARIA_TYPE_REJECT,
282+
"Emphasized": BUTTON_ARIA_TYPE_EMPHASIZED,
283+
};
284+
}
285+
286+
get buttonTypeText() {
287+
return this.resourceBundle.getText(Button.typeTextMappings()[this.design]);
288+
}
289+
269290
static async define(...params) {
270-
await Icon.define();
291+
await Promise.all([
292+
Icon.define(),
293+
fetchResourceBundle("@ui5/webcomponents"),
294+
]);
271295

272296
super.define(...params);
273297
}

packages/main/src/CheckBox.hbs

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
aria-checked="{{checked}}"
55
aria-readonly="{{ariaReadonly}}"
66
aria-disabled="{{ariaDisabled}}"
7+
aria-labelledby="{{ariaLabelledBy}}"
8+
aria-describedby="{{ariaDescribedBy}}"
79
tabindex="{{tabIndex}}"
810
dir="{{rtl}}"
911
>
@@ -16,7 +18,11 @@
1618
</div>
1719

1820
{{#if _label.text}}
19-
<ui5-label class="ui5-checkbox-label" ?wrap="{{_label.wrap}}">{{_label.text}}</ui5-label>
21+
<ui5-label id="{{_id}}-label" class="ui5-checkbox-label" ?wrap="{{_label.wrap}}">{{_label.text}}</ui5-label>
22+
{{/if}}
23+
24+
{{#if hasValueState}}
25+
<span id="{{_id}}-descr" class="ui5-hidden-text">{{valueStateText}}</span>
2026
{{/if}}
2127

2228
<slot name="formSupport"></slot>

packages/main/src/CheckBox.js

+24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/events/PseudoEven
99
import "@ui5/webcomponents-base/dist/icons/accept.js";
1010
import Icon from "./Icon.js";
1111
import Label from "./Label.js";
12+
import { VALUE_STATE_ERROR, VALUE_STATE_WARNING } from "./i18n/defaults.js";
1213

1314
// Template
1415
import CheckBoxTemplate from "./generated/templates/CheckBoxTemplate.lit.js";
@@ -282,6 +283,29 @@ class CheckBox extends UI5Element {
282283
return this.disabled ? "true" : undefined;
283284
}
284285

286+
get ariaLabelledBy() {
287+
return this.text ? `${this._id}-label` : undefined;
288+
}
289+
290+
get ariaDescribedBy() {
291+
return this.hasValueState ? `${this._id}-descr` : undefined;
292+
}
293+
294+
get hasValueState() {
295+
return this.valueState !== ValueState.None;
296+
}
297+
298+
static valueStateTextMappings() {
299+
return {
300+
"Error": VALUE_STATE_ERROR.defaultText,
301+
"Warning": VALUE_STATE_WARNING.defaultText,
302+
};
303+
}
304+
305+
get valueStateText() {
306+
return CheckBox.valueStateTextMappings()[this.valueState];
307+
}
308+
285309
get tabIndex() {
286310
return this.disabled ? undefined : "0";
287311
}

packages/main/src/Icon.hbs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
class="ui5-icon-root"
33
dir="{{dir}}"
44
viewBox="0 0 512 512"
5-
5+
role="img"
66
preserveAspectRatio="xMidYMid meet"
77
xmlns="http://www.w3.org/2000/svg"
88
>
9-
<g>
9+
<g role="presentation" aria-hidden="true">
1010
<path transform="translate(0, 512) scale(1, -1)" d={{d}} />
1111
</g>
12-
</svg>
12+
</svg>

packages/main/src/Input.hbs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<div
22
class="ui5-input-root"
3-
?aria-invalid="{{ariaInvalid}}"
43
@focusin="{{onfocusin}}"
54
@focusout="{{onfocusout}}"
65
>
@@ -14,8 +13,13 @@
1413
type="{{inputType}}"
1514
?disabled="{{disabled}}"
1615
?readonly="{{_readonly}}"
16+
?required="{{required}}"
1717
.value="{{value}}"
1818
placeholder="{{inputPlaceholder}}"
19+
aria-invalid="{{ariaInvalid}}"
20+
aria-haspopup="{{ariaHasPopup}}"
21+
aria-describedby="{{ariaDescribedBy}}"
22+
aria-autocomplete="{{ariaAutoComplete}}"
1923
@input="{{_handleInput}}"
2024
@change="{{_handleChange}}"
2125
data-sap-no-tab-ref
@@ -26,6 +30,14 @@
2630
<slot name="icon"></slot>
2731
</div>
2832
{{/if}}
33+
34+
{{#if showSuggestions}}
35+
<span id="{{_id}}-suggestionsText" class="ui5-hidden-text">{{suggestionsText}}</span>
36+
{{/if}}
37+
38+
{{#if hasValueState}}
39+
<span id="{{_id}}-descr" class="ui5-hidden-text">{{valueStateText}}</span>
40+
{{/if}}
2941
</div>
3042

3143
{{#if showSuggestions}}

packages/main/src/Input.js

+52-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import InputType from "./types/InputType.js";
1414
// Template
1515
import InputTemplate from "./generated/templates/InputTemplate.lit.js";
1616

17+
import {
18+
VALUE_STATE_SUCCESS,
19+
VALUE_STATE_ERROR,
20+
VALUE_STATE_WARNING,
21+
INPUT_SUGGESTIONS,
22+
} from "./i18n/defaults.js";
23+
1724
// Styles
1825
import styles from "./generated/themes/Input.css.js";
1926

@@ -103,6 +110,18 @@ const metadata = {
103110
type: Boolean,
104111
},
105112

113+
/**
114+
* Defines whether the <code>ui5-input</code> is required.
115+
*
116+
* @type {boolean}
117+
* @defaultvalue false
118+
* @public
119+
* @since 1.0.0
120+
*/
121+
required: {
122+
type: Boolean,
123+
},
124+
106125
/**
107126
* Defines the HTML type of the <code>ui5-input</code>.
108127
* Available options are: <code>Text</code>, <code>Email</code>,
@@ -529,7 +548,39 @@ class Input extends UI5Element {
529548
}
530549

531550
get ariaInvalid() {
532-
return this.valueState === "Error" ? "true" : undefined;
551+
return this.valueState === ValueState.Error ? "true" : undefined;
552+
}
553+
554+
get ariaDescribedBy() {
555+
return this.showSuggestions ? `${this._id}-suggestionsText` : undefined;
556+
}
557+
558+
get ariaHasPopup() {
559+
return this.showSuggestions ? "true" : undefined;
560+
}
561+
562+
get ariaAutoComplete() {
563+
return this.showSuggestions ? "list" : undefined;
564+
}
565+
566+
get hasValueState() {
567+
return this.valueState !== ValueState.None;
568+
}
569+
570+
static valueStateTextMappings() {
571+
return {
572+
"Success": VALUE_STATE_SUCCESS.defaultText,
573+
"Error": VALUE_STATE_ERROR.defaultText,
574+
"Warning": VALUE_STATE_WARNING.defaultText,
575+
};
576+
}
577+
578+
get valueStateText() {
579+
return Input.valueStateTextMappings()[this.valueState];
580+
}
581+
582+
get suggestionsText() {
583+
return INPUT_SUGGESTIONS.defaultText;
533584
}
534585
}
535586

packages/main/src/Link.hbs

+3
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
?disabled="{{disabled}}"
99
aria-disabled="{{ariaDisabled}}">
1010
<slot></slot>
11+
{{#if hasLinkType}}
12+
<span class="ui5-hidden-text">{{linkTypeText}}</span>
13+
{{/if}}
1114
</a>

packages/main/src/Link.js

+25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
3+
import { fetchResourceBundle, getResourceBundle } from "@ui5/webcomponents-base/dist/ResourceBundle.js";
34
import LinkDesign from "./types/LinkDesign.js";
45

56
// Template
67
import LinkRederer from "./generated/templates/LinkTemplate.lit.js";
78

9+
import { LINK_SUBTLE, LINK_EMPHASIZED } from "./i18n/defaults.js";
10+
811
// Styles
912
import linkCss from "./generated/themes/Link.css.js";
1013

@@ -160,6 +163,7 @@ class Link extends UI5Element {
160163
constructor() {
161164
super();
162165
this._dummyAnchor = document.createElement("a");
166+
this.resourceBundle = getResourceBundle("@ui5/webcomponents");
163167
}
164168

165169
static get metadata() {
@@ -204,9 +208,30 @@ class Link extends UI5Element {
204208
return this.disabled ? "true" : undefined;
205209
}
206210

211+
get hasLinkType() {
212+
return this.design !== LinkDesign.Default;
213+
}
214+
215+
static typeTextMappings() {
216+
return {
217+
"Subtle": LINK_SUBTLE,
218+
"Emphasized": LINK_EMPHASIZED,
219+
};
220+
}
221+
222+
get linkTypeText() {
223+
return this.resourceBundle.getText(Link.typeTextMappings()[this.design]);
224+
}
225+
207226
get parsedRef() {
208227
return this.href.length > 0 ? this.href : undefined;
209228
}
229+
230+
static async define(...params) {
231+
await fetchResourceBundle("@ui5/webcomponents");
232+
233+
super.define(...params);
234+
}
210235
}
211236

212237
Link.define();

packages/main/src/MessageStrip.hbs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<div class="{{classes.main}}"
2+
id="{{_id}}"
23
role="alert"
34
aria-live="assertive"
45
aria-labelledby="{{_id}}">
@@ -17,7 +18,7 @@
1718
@click={{_closeClick}}
1819
>
1920
<ui5-icon
20-
class="ui5-messagestrip-close-icon"
21+
class="ui5-messagestrip-close-icon"
2122
src="sap-icon://decline"
2223
>
2324
</ui5-icon>

0 commit comments

Comments
 (0)