-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(invisibleMessage): introduce invisibleMessage util #3192
Changes from 4 commits
2e890eb
6a490fd
4589c6f
8e4b8a0
f5bd3db
307974f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import DataType from "./DataType.js"; | ||
|
||
/** | ||
* Enumeration for different mode behaviors of the <code>InvisibleMessage</code>. | ||
* @private | ||
*/ | ||
const InvisibleMessageModes = { | ||
|
||
/** | ||
* Indicates that updates to the region should be presented at the next graceful opportunity, | ||
* such as at the end of reading the current sentence, or when the user pauses typing. | ||
*/ | ||
Polite: "Polite", | ||
|
||
/** | ||
* Indicates that updates to the region have the highest priority and should be presented to the user immediately. | ||
*/ | ||
Assertive: "Assertive", | ||
|
||
}; | ||
|
||
class InvisibleMessageMode extends DataType { | ||
static isValid(value) { | ||
return !!InvisibleMessageModes[value]; | ||
} | ||
} | ||
|
||
InvisibleMessageMode.generateTypeAccessors(InvisibleMessageModes); | ||
|
||
export default InvisibleMessageModes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import InvisibleMessageMode from "../types/InvisibleMessageMode.js"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file does not belong to |
||
import getSingletonElementInstance from "./getSingletonElementInstance.js"; | ||
|
||
const styles = `position: absolute; | ||
ivoplashkov marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole code is not CSP compliant. We cannot set CSS like this (innerText, cssText, etc...). Use the imperative APIs instead. |
||
clip: rect(1px,1px,1px,1px); | ||
user-select: none; | ||
left: -1000px; | ||
top: -1000px; | ||
pointer-events: none;`; | ||
|
||
const politeSpan = document.createElement("span"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code must be synchronized with the lifecycle of the framework. You can achieve this in one of two ways:
I prefer the first. |
||
const assertiveSpan = document.createElement("span"); | ||
|
||
politeSpan.classList.add("ui5-invisiblemessage-polite"); | ||
assertiveSpan.classList.add("ui5-invisiblemessage-assertive"); | ||
|
||
politeSpan.setAttribute("aria-live", "polite"); | ||
assertiveSpan.setAttribute("aria-live", "assertive"); | ||
|
||
politeSpan.setAttribute("role", "alert"); | ||
assertiveSpan.setAttribute("role", "alert"); | ||
|
||
politeSpan.style.cssText = styles; | ||
assertiveSpan.style.cssText = styles; | ||
|
||
if (!politeSpan.parentElement) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't this be always true? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check if you have pushed the changes. |
||
getSingletonElementInstance("ui5-static-area").appendChild(politeSpan); | ||
getSingletonElementInstance("ui5-static-area").appendChild(assertiveSpan); | ||
} | ||
|
||
/** | ||
* Inserts the string into the respective span, depending on the mode provided. | ||
* | ||
* @param {string} message String to be announced by the screen reader. | ||
* @param {sap.ui.core.InvisibleMessageMode} mode The mode to be inserted in the aria-live attribute. | ||
*/ | ||
const announce = (message, mode) => { | ||
// If no type is presented, fallback to polite announcement. | ||
const span = mode === InvisibleMessageMode.Assertive ? assertiveSpan : politeSpan; | ||
|
||
// Set textContent to empty string in order to trigger screen reader's announcement. | ||
span.textContent = ""; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why you'd need to make it empty and then assign the message? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If someone wants to announce the same message over and over again on some interaction, the screen reader won't be triggered if we don't empty the value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure that way, it'd be read out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could would then become: |
||
span.textContent = message; | ||
|
||
if (mode !== InvisibleMessageMode.Assertive && mode !== InvisibleMessageMode.Polite) { | ||
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 | ||
} | ||
}; | ||
|
||
export default announce; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | ||
|
||
<title>InvisibleMessage</title> | ||
|
||
<script src="../../../webcomponentsjs/webcomponents-loader.js"></script> | ||
<script src="../../../resources/bundle.esm.js" type="module"></script> | ||
<script nomodule src="../../../resources/bundle.es5.js"></script> | ||
</head> | ||
|
||
<body style="background-color: var(--sapBackgroundColor);"> | ||
<style> | ||
ui5-textarea { | ||
margin: 1rem 0; | ||
} | ||
</style> | ||
|
||
<section class="group"> | ||
<ui5-title>InvisibleMessage announcement</ui5-title> | ||
<ui5-textarea id="announce-textarea" placeholder="Enter text to be announced by the screen reader."></ui5-textarea> | ||
<ui5-checkbox id="announce-checkbox" text="Assertive announcement"></ui5-checkbox> | ||
<ui5-button id="announce-button" design="Emphasized" aria-expanded="true">Press me to announce.</ui5-button> | ||
</section> | ||
|
||
<script> | ||
const button = document.querySelector("#announce-button"); | ||
const textarea = document.querySelector("#announce-textarea"); | ||
const checkbox = document.querySelector("#announce-checkbox"); | ||
let invisibleMessage; | ||
|
||
button.addEventListener("click", function(event) { | ||
invisibleMessage = window["sap-ui-webcomponents-bundle"].invisibleMessage; | ||
|
||
if (checkbox.checked) { | ||
invisibleMessage.announce(textarea.value, "Assertive") | ||
} else { | ||
invisibleMessage.announce(textarea.value) | ||
} | ||
}); | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
const assert = require("chai").assert; | ||
const PORT = require("../_port.js"); | ||
|
||
describe("InvisibleMessage", () => { | ||
before(() => { | ||
browser.url(`http://localhost:${PORT}/test-resources/pages/base/InvisibleMessage.html`); | ||
}); | ||
|
||
it("Initial rendering", () => { | ||
const politeSpan = browser.$(".ui5-invisiblemessage-polite"); | ||
const assertiveSpan = browser.$(".ui5-invisiblemessage-assertive"); | ||
|
||
assert.ok(politeSpan, "polite span is rendered"); | ||
assert.ok(assertiveSpan, "assertive span not rendered"); | ||
}); | ||
|
||
it("String annoucement", () => { | ||
const politeSpan = browser.$(".ui5-invisiblemessage-polite"); | ||
const assertiveSpan = browser.$(".ui5-invisiblemessage-assertive"); | ||
const button = browser.$("#announce-button"); | ||
const checkBox = browser.$("#announce-checkbox"); | ||
|
||
browser.execute(() => { | ||
document.getElementById("announce-textarea").value = "announcement"; | ||
}); | ||
|
||
button.click(); | ||
checkBox.click(); | ||
button.click(); | ||
|
||
assert.ok(politeSpan.getHTML().indexOf("announcement") > -1, "value has been announced"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not true There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check if you have pushed the changes. |
||
assert.ok(assertiveSpan.getHTML().indexOf("announcement") > -1, "value has been announced"); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be called
AnnouncementMode
IMO