UNPKG

activator-oce-exporter

Version:

Extract Activator binder and convert it to valid OCE mono pacakge

388 lines (365 loc) 11.3 kB
/** @license Copyright (c) 2017 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ 'use strict'; import {nativeShadow, nativeCssVariables} from './style-settings.js'; import {parse, stringify, types, StyleNode} from './css-parse.js'; // eslint-disable-line no-unused-vars import {MEDIA_MATCH} from './common-regex.js'; import {processUnscopedStyle, isUnscopedStyle} from './unscoped-style-handler.js'; /** * @param {string|StyleNode} rules * @param {function(StyleNode)=} callback * @return {string} */ export function toCssText (rules, callback) { if (!rules) { return ''; } if (typeof rules === 'string') { rules = parse(rules); } if (callback) { forEachRule(rules, callback); } return stringify(rules, nativeCssVariables); } /** * @param {HTMLStyleElement} style * @return {StyleNode} */ export function rulesForStyle(style) { if (!style['__cssRules'] && style.textContent) { style['__cssRules'] = parse(style.textContent); } return style['__cssRules'] || null; } // Tests if a rule is a keyframes selector, which looks almost exactly // like a normal selector but is not (it has nothing to do with scoping // for example). /** * @param {StyleNode} rule * @return {boolean} */ export function isKeyframesSelector(rule) { return Boolean(rule['parent']) && rule['parent']['type'] === types.KEYFRAMES_RULE; } /** * @param {StyleNode} node * @param {Function=} styleRuleCallback * @param {Function=} keyframesRuleCallback * @param {boolean=} onlyActiveRules */ export function forEachRule(node, styleRuleCallback, keyframesRuleCallback, onlyActiveRules) { if (!node) { return; } let skipRules = false; let type = node['type']; if (onlyActiveRules) { if (type === types.MEDIA_RULE) { let matchMedia = node['selector'].match(MEDIA_MATCH); if (matchMedia) { // if rule is a non matching @media rule, skip subrules if (!window.matchMedia(matchMedia[1]).matches) { skipRules = true; } } } } if (type === types.STYLE_RULE) { styleRuleCallback(node); } else if (keyframesRuleCallback && type === types.KEYFRAMES_RULE) { keyframesRuleCallback(node); } else if (type === types.MIXIN_RULE) { skipRules = true; } let r$ = node['rules']; if (r$ && !skipRules) { for (let i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { forEachRule(r, styleRuleCallback, keyframesRuleCallback, onlyActiveRules); } } } // add a string of cssText to the document. /** * @param {string} cssText * @param {string} moniker * @param {Node} target * @param {Node} contextNode * @return {HTMLStyleElement} */ export function applyCss(cssText, moniker, target, contextNode) { let style = createScopeStyle(cssText, moniker); applyStyle(style, target, contextNode); return style; } /** * @param {string} cssText * @param {string} moniker * @return {HTMLStyleElement} */ export function createScopeStyle(cssText, moniker) { let style = /** @type {HTMLStyleElement} */(document.createElement('style')); if (moniker) { style.setAttribute('scope', moniker); } style.textContent = cssText; return style; } /** * Track the position of the last added style for placing placeholders * @type {Node} */ let lastHeadApplyNode = null; // insert a comment node as a styling position placeholder. /** * @param {string} moniker * @return {!Comment} */ export function applyStylePlaceHolder(moniker) { let placeHolder = document.createComment(' Shady DOM styles for ' + moniker + ' '); let after = lastHeadApplyNode ? lastHeadApplyNode['nextSibling'] : null; let scope = document.head; scope.insertBefore(placeHolder, after || scope.firstChild); lastHeadApplyNode = placeHolder; return placeHolder; } /** * @param {HTMLStyleElement} style * @param {?Node} target * @param {?Node} contextNode */ export function applyStyle(style, target, contextNode) { target = target || document.head; let after = (contextNode && contextNode.nextSibling) || target.firstChild; target.insertBefore(style, after); if (!lastHeadApplyNode) { lastHeadApplyNode = style; } else { // only update lastHeadApplyNode if the new style is inserted after the old lastHeadApplyNode let position = style.compareDocumentPosition(lastHeadApplyNode); if (position === Node.DOCUMENT_POSITION_PRECEDING) { lastHeadApplyNode = style; } } } /** * @param {string} buildType * @return {boolean} */ export function isTargetedBuild(buildType) { return nativeShadow ? buildType === 'shadow' : buildType === 'shady'; } /** * Walk from text[start] matching parens and * returns position of the outer end paren * @param {string} text * @param {number} start * @return {number} */ export function findMatchingParen(text, start) { let level = 0; for (let i=start, l=text.length; i < l; i++) { if (text[i] === '(') { level++; } else if (text[i] === ')') { if (--level === 0) { return i; } } } return -1; } /** * @param {string} str * @param {function(string, string, string, string)} callback */ export function processVariableAndFallback(str, callback) { // find 'var(' let start = str.indexOf('var('); if (start === -1) { // no var?, everything is prefix return callback(str, '', '', ''); } //${prefix}var(${inner})${suffix} let end = findMatchingParen(str, start + 3); let inner = str.substring(start + 4, end); let prefix = str.substring(0, start); // suffix may have other variables let suffix = processVariableAndFallback(str.substring(end + 1), callback); let comma = inner.indexOf(','); // value and fallback args should be trimmed to match in property lookup if (comma === -1) { // variable, no fallback return callback(prefix, inner.trim(), '', suffix); } // var(${value},${fallback}) let value = inner.substring(0, comma).trim(); let fallback = inner.substring(comma + 1).trim(); return callback(prefix, value, fallback, suffix); } /** * @param {Element} element * @param {string} value */ export function setElementClassRaw(element, value) { // use native setAttribute provided by ShadyDOM when setAttribute is patched if (nativeShadow) { element.setAttribute('class', value); } else { window['ShadyDOM']['nativeMethods']['setAttribute'].call(element, 'class', value); } } /** * @param {Element | {is: string, extends: string}} element * @return {{is: string, typeExtension: string}} */ export function getIsExtends(element) { let localName = element['localName']; let is = '', typeExtension = ''; /* NOTE: technically, this can be wrong for certain svg elements with `-` in the name like `<font-face>` */ if (localName) { if (localName.indexOf('-') > -1) { is = localName; } else { typeExtension = localName; is = (element.getAttribute && element.getAttribute('is')) || ''; } } else { is = /** @type {?} */(element).is; typeExtension = /** @type {?} */(element).extends; } return {is, typeExtension}; } /** * @param {Element|DocumentFragment} element * @return {string} */ export function gatherStyleText(element) { /** @type {!Array<string>} */ const styleTextParts = []; const styles = /** @type {!NodeList<!HTMLStyleElement>} */(element.querySelectorAll('style')); for (let i = 0; i < styles.length; i++) { const style = styles[i]; if (isUnscopedStyle(style)) { if (!nativeShadow) { processUnscopedStyle(style); style.parentNode.removeChild(style); } } else { styleTextParts.push(style.textContent); style.parentNode.removeChild(style); } } return styleTextParts.join('').trim(); } /** * Split a selector separated by commas into an array in a smart way * @param {string} selector * @return {!Array<string>} */ export function splitSelectorList(selector) { const parts = []; let part = ''; for (let i = 0; i >= 0 && i < selector.length; i++) { // A selector with parentheses will be one complete part if (selector[i] === '(') { // find the matching paren const end = findMatchingParen(selector, i); // push the paren block into the part part += selector.slice(i, end + 1); // move the index to after the paren block i = end; } else if (selector[i] === ',') { parts.push(part); part = ''; } else { part += selector[i]; } } // catch any pieces after the last comma if (part) { parts.push(part); } return parts; } const CSS_BUILD_ATTR = 'css-build'; /** * Return the polymer-css-build "build type" applied to this element * * @param {!HTMLElement} element * @return {string} Can be "", "shady", or "shadow" */ export function getCssBuild(element) { if (element.__cssBuild === undefined) { // try attribute first, as it is the common case const attrValue = element.getAttribute(CSS_BUILD_ATTR); if (attrValue) { element.__cssBuild = attrValue; } else { const buildComment = getBuildComment(element); if (buildComment !== '') { // remove build comment so it is not needlessly copied into every element instance removeBuildComment(element); } element.__cssBuild = buildComment; } } return element.__cssBuild || ''; } /** * Check if the given element, either a <template> or <style>, has been processed * by polymer-css-build. * * If so, then we can make a number of optimizations: * - polymer-css-build will decompose mixins into individual CSS Custom Properties, * so the ApplyShim can be skipped entirely. * - Under native ShadowDOM, the style text can just be copied into each instance * without modification * - If the build is "shady" and ShadyDOM is in use, the styling does not need * scoping beyond the shimming of CSS Custom Properties * * @param {!HTMLElement} element * @return {boolean} */ export function elementHasBuiltCss(element) { return getCssBuild(element) !== ''; } /** * For templates made with tagged template literals, polymer-css-build will * insert a comment of the form `<!--css-build:shadow-->` * * @param {!HTMLElement} element * @return {string} */ export function getBuildComment(element) { const buildComment = element.localName === 'template' ? element.content.firstChild : element.firstChild; if (buildComment instanceof Comment) { const commentParts = buildComment.textContent.trim().split(':'); if (commentParts[0] === CSS_BUILD_ATTR) { return commentParts[1]; } } return ''; } /** * @param {!HTMLElement} element */ function removeBuildComment(element) { const buildComment = element.localName === 'template' ? element.content.firstChild : element.firstChild; buildComment.parentNode.removeChild(buildComment); }