@webcomponents/custom-elements
Version:
HTML Custom Elements Polyfill
130 lines (116 loc) • 3.98 kB
JavaScript
const reservedTagList = new Set([
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph',
]);
/**
* @param {string} localName
* @returns {boolean}
*/
export function isValidCustomElementName(localName) {
const reserved = reservedTagList.has(localName);
const validForm = /^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(localName);
return !reserved && validForm;
}
/**
* @private
* @param {!Node} node
* @return {boolean}
*/
export function isConnected(node) {
// Use `Node#isConnected`, if defined.
const nativeValue = node.isConnected;
if (nativeValue !== undefined) {
return nativeValue;
}
/** @type {?Node|undefined} */
let current = node;
while (current && !(current.__CE_isImportDocument || current instanceof Document)) {
current = current.parentNode || (window.ShadowRoot && current instanceof ShadowRoot ? current.host : undefined);
}
return !!(current && (current.__CE_isImportDocument || current instanceof Document));
}
/**
* @param {!Node} root
* @param {!Node} start
* @return {?Node}
*/
function nextSiblingOrAncestorSibling(root, start) {
let node = start;
while (node && node !== root && !node.nextSibling) {
node = node.parentNode;
}
return (!node || node === root) ? null : node.nextSibling;
}
/**
* @param {!Node} root
* @param {!Node} start
* @return {?Node}
*/
function nextNode(root, start) {
return start.firstChild ? start.firstChild : nextSiblingOrAncestorSibling(root, start);
}
/**
* @param {!Node} root
* @param {!function(!Element)} callback
* @param {!Set<Node>=} visitedImports
*/
export function walkDeepDescendantElements(root, callback, visitedImports = new Set()) {
let node = root;
while (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = /** @type {!Element} */(node);
callback(element);
const localName = element.localName;
if (localName === 'link' && element.getAttribute('rel') === 'import') {
// If this import (polyfilled or not) has it's root node available,
// walk it.
const importNode = /** @type {!Node} */ (element.import);
if (importNode instanceof Node && !visitedImports.has(importNode)) {
// Prevent multiple walks of the same import root.
visitedImports.add(importNode);
for (let child = importNode.firstChild; child; child = child.nextSibling) {
walkDeepDescendantElements(child, callback, visitedImports);
}
}
// Ignore descendants of import links to prevent attempting to walk the
// elements created by the HTML Imports polyfill that we just walked
// above.
node = nextSiblingOrAncestorSibling(root, element);
continue;
} else if (localName === 'template') {
// Ignore descendants of templates. There shouldn't be any descendants
// because they will be moved into `.content` during construction in
// browsers that support template but, in case they exist and are still
// waiting to be moved by a polyfill, they will be ignored.
node = nextSiblingOrAncestorSibling(root, element);
continue;
}
// Walk shadow roots.
const shadowRoot = element.__CE_shadowRoot;
if (shadowRoot) {
for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
walkDeepDescendantElements(child, callback, visitedImports);
}
}
}
node = nextNode(root, node);
}
}
/**
* Used to suppress Closure's "Modifying the prototype is only allowed if the
* constructor is in the same scope" warning without using
* `@suppress {newCheckTypes, duplicate}` because `newCheckTypes` is too broad.
*
* @param {!Object} destination
* @param {string} name
* @param {*} value
*/
export function setPropertyUnchecked(destination, name, value) {
destination[name] = value;
}