UNPKG

@webcomponents/custom-elements

Version:
130 lines (116 loc) 3.98 kB
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; }