Skip to content

Commit 0a33c40

Browse files
authored
feat(invisibleMessage): introduce invisibleMessage util (#3192)
* feat(invisibleMessage): introduce invisibleMessage util * apply comments * fix typo * apply comments
1 parent 6495028 commit 0a33c40

File tree

5 files changed

+162
-0
lines changed

5 files changed

+162
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import DataType from "./DataType.js";
2+
3+
/**
4+
* Enumeration for different mode behaviors of the <code>InvisibleMessage</code>.
5+
* @private
6+
*/
7+
const InvisibleMessageModes = {
8+
9+
/**
10+
* Indicates that updates to the region should be presented at the next graceful opportunity,
11+
* such as at the end of reading the current sentence, or when the user pauses typing.
12+
*/
13+
Polite: "Polite",
14+
15+
/**
16+
* Indicates that updates to the region have the highest priority and should be presented to the user immediately.
17+
*/
18+
Assertive: "Assertive",
19+
20+
};
21+
22+
class InvisibleMessageMode extends DataType {
23+
static isValid(value) {
24+
return !!InvisibleMessageModes[value];
25+
}
26+
}
27+
28+
InvisibleMessageMode.generateTypeAccessors(InvisibleMessageModes);
29+
30+
export default InvisibleMessageModes;
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import InvisibleMessageMode from "../types/InvisibleMessageMode.js";
2+
import getSingletonElementInstance from "./getSingletonElementInstance.js";
3+
4+
const styles = `position: absolute;
5+
clip: rect(1px,1px,1px,1px);
6+
user-select: none;
7+
left: -1000px;
8+
top: -1000px;
9+
pointer-events: none;`;
10+
11+
const politeSpan = document.createElement("span");
12+
const assertiveSpan = document.createElement("span");
13+
14+
politeSpan.classList.add("ui5-invisiblemessage-polite");
15+
assertiveSpan.classList.add("ui5-invisiblemessage-assertive");
16+
17+
politeSpan.setAttribute("aria-live", "polite");
18+
assertiveSpan.setAttribute("aria-live", "assertive");
19+
20+
politeSpan.setAttribute("role", "alert");
21+
assertiveSpan.setAttribute("role", "alert");
22+
23+
politeSpan.style.cssText = styles;
24+
assertiveSpan.style.cssText = styles;
25+
26+
getSingletonElementInstance("ui5-static-area").appendChild(politeSpan);
27+
getSingletonElementInstance("ui5-static-area").appendChild(assertiveSpan);
28+
29+
/**
30+
* Inserts the string into the respective span, depending on the mode provided.
31+
*
32+
* @param {string} message String to be announced by the screen reader.
33+
* @param {sap.ui.core.InvisibleMessageMode} mode The mode to be inserted in the aria-live attribute.
34+
*/
35+
const announce = (message, mode) => {
36+
// If no type is presented, fallback to polite announcement.
37+
const span = mode === InvisibleMessageMode.Assertive ? assertiveSpan : politeSpan;
38+
39+
// Set textContent to empty string in order to trigger screen reader's announcement.
40+
span.textContent = "";
41+
span.textContent = message;
42+
43+
if (mode !== InvisibleMessageMode.Assertive && mode !== InvisibleMessageMode.Polite) {
44+
console.warn(`You have entered an invalid mode. Valid values are: "Polite" and "Assertive". The framework will automatically set the mode to "Polite".`); // eslint-disable-line
45+
}
46+
};
47+
48+
export default announce;

packages/main/bundle.common.js

+4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import applyDirection from "@ui5/webcomponents-base/dist/locale/applyDirection.j
103103
import { attachDirectionChange } from "@ui5/webcomponents-base/dist/locale/directionChange.js";
104104
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
105105
import * as defaultTexts from "./dist/generated/i18n/i18n-defaults.js";
106+
import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js";
106107

107108
const testAssets = {
108109
configuration : {
@@ -118,6 +119,9 @@ const testAssets = {
118119
getAssetsPath,
119120
setAssetsPath
120121
},
122+
invisibleMessage : {
123+
announce,
124+
},
121125
getLocaleData,
122126
applyDirection,
123127
attachDirectionChange,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7+
8+
<title>InvisibleMessage</title>
9+
10+
<script src="../../../webcomponentsjs/webcomponents-loader.js"></script>
11+
<script src="../../../resources/bundle.esm.js" type="module"></script>
12+
<script nomodule src="../../../resources/bundle.es5.js"></script>
13+
</head>
14+
15+
<body style="background-color: var(--sapBackgroundColor);">
16+
<style>
17+
ui5-textarea {
18+
margin: 1rem 0;
19+
}
20+
</style>
21+
22+
<section class="group">
23+
<ui5-title>InvisibleMessage announcement</ui5-title>
24+
<ui5-textarea id="announce-textarea" placeholder="Enter text to be announced by the screen reader."></ui5-textarea>
25+
<ui5-checkbox id="announce-checkbox" text="Assertive announcement"></ui5-checkbox>
26+
<ui5-button id="announce-button" design="Emphasized" aria-expanded="true">Press me to announce.</ui5-button>
27+
</section>
28+
29+
<script>
30+
const button = document.querySelector("#announce-button");
31+
const textarea = document.querySelector("#announce-textarea");
32+
const checkbox = document.querySelector("#announce-checkbox");
33+
let invisibleMessage;
34+
35+
button.addEventListener("click", function(event) {
36+
invisibleMessage = window["sap-ui-webcomponents-bundle"].invisibleMessage;
37+
38+
if (checkbox.checked) {
39+
invisibleMessage.announce(textarea.value, "Assertive")
40+
} else {
41+
invisibleMessage.announce(textarea.value)
42+
}
43+
});
44+
</script>
45+
</body>
46+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const assert = require("chai").assert;
2+
const PORT = require("../_port.js");
3+
4+
describe("InvisibleMessage", () => {
5+
before(() => {
6+
browser.url(`http://localhost:${PORT}/test-resources/pages/base/InvisibleMessage.html`);
7+
});
8+
9+
it("Initial rendering", () => {
10+
const politeSpan = browser.$(".ui5-invisiblemessage-polite");
11+
const assertiveSpan = browser.$(".ui5-invisiblemessage-assertive");
12+
13+
assert.ok(politeSpan, "Polite span is rendered");
14+
assert.ok(assertiveSpan, "Assertive span is rendered");
15+
});
16+
17+
it("String annoucement", () => {
18+
const politeSpan = browser.$(".ui5-invisiblemessage-polite");
19+
const assertiveSpan = browser.$(".ui5-invisiblemessage-assertive");
20+
const button = browser.$("#announce-button");
21+
const checkBox = browser.$("#announce-checkbox");
22+
23+
browser.execute(() => {
24+
document.getElementById("announce-textarea").value = "announcement";
25+
});
26+
27+
button.click();
28+
checkBox.click();
29+
button.click();
30+
31+
assert.ok(politeSpan.getHTML().indexOf("announcement") > -1, "Value has been rendered.");
32+
assert.ok(assertiveSpan.getHTML().indexOf("announcement") > -1, "Value has been rendered.");
33+
});
34+
});

0 commit comments

Comments
 (0)