blob: 1ae4452e030c6f20dc58a589f667fb8b967f13c7 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview 'settings-localized-link' takes a localized string that
* contains up to one anchor tag, and labels the string contained within the
* anchor tag with the entire localized string. The string should not be bound
* by element tags. The string should not contain any elements other than the
* single anchor tagged element that will be aria-labelledby the entire string.
*
* Example: "lorem ipsum <a href="example.com">Learn More</a> dolor sit"
*
* The "Learn More" will be aria-labelledby like so: "lorem ipsum Learn More
* dolor sit". Meanwhile, "Lorem ipsum" and "dolor sit" will be aria-hidden.
*
* This element also supports strings that do not contain anchor tags; in this
* case, the element gracefully falls back to normal text. This can be useful
* when the property is data-bound to a function which sometimes returns a
* string with a link and sometimes returns a normal string.
*/
Polymer({
is: 'settings-localized-link',
properties: {
/**
* The localized string that contains up to one anchor tag, the text
* within which will be aria-labelledby the entire localizedString.
*/
localizedString: String,
/**
* If provided, the URL that the anchor tag will point to. There is no
* need to provide a linkUrl if the URL is embedded in the localizedString.
*/
linkUrl: {
type: String,
value: '',
},
},
/**
* Attaches aria attributes and optionally provided link to the provided
* localizedString.
* @param {string} localizedString
* @param {string} linkUrl
* @return {string} localizedString formatted with additional ids, spans,
* and an aria-labelledby tag
* @private
*/
getAriaLabelledContent_(localizedString, linkUrl) {
const tempEl = document.createElement('div');
tempEl.innerHTML = localizedString;
const ariaLabelledByIds = [];
tempEl.childNodes.forEach((node, index) => {
// Text nodes should be aria-hidden and associated with an element id
// that the anchor element can be aria-labelledby.
if (node.nodeType == Node.TEXT_NODE) {
const spanNode = document.createElement('span');
spanNode.textContent = node.textContent;
spanNode.id = `id${index}`;
ariaLabelledByIds.push(spanNode.id);
spanNode.setAttribute('aria-hidden', true);
node.replaceWith(spanNode);
return;
}
// The single element node with anchor tags should also be aria-labelledby
// itself in-order with respect to the entire string.
if (node.nodeType == Node.ELEMENT_NODE && node.nodeName == 'A') {
node.id = `id${index}`;
ariaLabelledByIds.push(node.id);
return;
}
// Only text and <a> nodes are allowed.
assertNotReached('settings-localized-link has invalid node types');
});
const anchorTags = tempEl.getElementsByTagName('a');
// In the event the provided localizedString contains only text nodes,
// populate the contents with the provided localizedString.
if (anchorTags.length == 0) {
return localizedString;
}
assert(anchorTags.length == 1,
'settings-localized-link should contain exactly one anchor tag');
const anchorTag = anchorTags[0];
anchorTag.setAttribute('aria-labelledby', ariaLabelledByIds.join(' '));
if (linkUrl != '') {
anchorTag.href = linkUrl;
anchorTag.target = '_blank';
}
return tempEl.innerHTML;
},
/** @override */
attached() {
const anchorTag = this.$$('a');
if (anchorTag) {
anchorTag.addEventListener('click', this.onAnchorTagClick_.bind(this));
}
},
/**
* @param {!Event} event
* @private
*/
onAnchorTagClick_(event) {
// Stop propagation of the event, since it has already been handled by
// opening the link.
event.stopPropagation();
},
});