UNPKG

i18n-behavior

Version:

Instant and Modular I18N engine for lit-html and Polymer

569 lines (563 loc) 949 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';const nativeShadow=!(window['ShadyDOM']&&window['ShadyDOM']['inUse']);let nativeCssVariables_;/** * @param {(ShadyCSSOptions | ShadyCSSInterface)=} settings */function calcCssVariables(settings){if(settings&&settings['shimcssproperties']){nativeCssVariables_=false;}else{// chrome 49 has semi-working css vars, check if box-shadow works // safari 9.1 has a recalc bug: https://bugs.webkit.org/show_bug.cgi?id=155782 // However, shim css custom properties are only supported with ShadyDOM enabled, // so fall back on native if we do not detect ShadyDOM // Edge 15: custom properties used in ::before and ::after will also be used in the parent element // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12414257/ nativeCssVariables_=nativeShadow||Boolean(!navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)&&window.CSS&&CSS.supports&&CSS.supports('box-shadow','0 0 0 var(--foo)'));}}/** @type {string | undefined} */let cssBuild;if(window.ShadyCSS&&window.ShadyCSS.cssBuild!==undefined){cssBuild=window.ShadyCSS.cssBuild;}if(window.ShadyCSS&&window.ShadyCSS.nativeCss!==undefined){nativeCssVariables_=window.ShadyCSS.nativeCss;}else if(window.ShadyCSS){calcCssVariables(window.ShadyCSS);// reset window variable to let ShadyCSS API take its place window.ShadyCSS=undefined;}else{calcCssVariables(window['WebComponents']&&window['WebComponents']['flags']);}// Hack for type error under new type inference which doesn't like that // nativeCssVariables is updated in a function and assigns the type // `function(): ?` instead of `boolean`. const nativeCssVariables=/** @type {boolean} */nativeCssVariables_;var styleSettings={nativeShadow:nativeShadow,get cssBuild(){return cssBuild;},nativeCssVariables:nativeCssVariables};/** @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 */ /* Extremely simple css parser. Intended to be not more than what we need and definitely not necessarily correct =). */'use strict';/** @unrestricted */class StyleNode{constructor(){/** @type {number} */this['start']=0;/** @type {number} */this['end']=0;/** @type {StyleNode} */this['previous']=null;/** @type {StyleNode} */this['parent']=null;/** @type {Array<StyleNode>} */this['rules']=null;/** @type {string} */this['parsedCssText']='';/** @type {string} */this['cssText']='';/** @type {boolean} */this['atRule']=false;/** @type {number} */this['type']=0;/** @type {string} */this['keyframesName']='';/** @type {string} */this['selector']='';/** @type {string} */this['parsedSelector']='';}}/** * @param {string} text * @return {StyleNode} */function parse(text){text=clean(text);return parseCss(lex(text),text);}// remove stuff we don't care about that may hinder parsing /** * @param {string} cssText * @return {string} */function clean(cssText){return cssText.replace(RX.comments,'').replace(RX.port,'');}// super simple {...} lexer that returns a node tree /** * @param {string} text * @return {StyleNode} */function lex(text){let root=new StyleNode();root['start']=0;root['end']=text.length;let n=root;for(let i=0,l=text.length;i<l;i++){if(text[i]===OPEN_BRACE){if(!n['rules']){n['rules']=[];}let p=n;let previous=p['rules'][p['rules'].length-1]||null;n=new StyleNode();n['start']=i+1;n['parent']=p;n['previous']=previous;p['rules'].push(n);}else if(text[i]===CLOSE_BRACE){n['end']=i+1;n=n['parent']||root;}}return root;}// add selectors/cssText to node tree /** * @param {StyleNode} node * @param {string} text * @return {StyleNode} */function parseCss(node,text){let t=text.substring(node['start'],node['end']-1);node['parsedCssText']=node['cssText']=t.trim();if(node['parent']){let ss=node['previous']?node['previous']['end']:node['parent']['start'];t=text.substring(ss,node['start']-1);t=_expandUnicodeEscapes(t);t=t.replace(RX.multipleSpaces,' ');// TODO(sorvell): ad hoc; make selector include only after last ; // helps with mixin syntax t=t.substring(t.lastIndexOf(';')+1);let s=node['parsedSelector']=node['selector']=t.trim();node['atRule']=s.indexOf(AT_START)===0;// note, support a subset of rule types... if(node['atRule']){if(s.indexOf(MEDIA_START)===0){node['type']=types.MEDIA_RULE;}else if(s.match(RX.keyframesRule)){node['type']=types.KEYFRAMES_RULE;node['keyframesName']=node['selector'].split(RX.multipleSpaces).pop();}}else{if(s.indexOf(VAR_START)===0){node['type']=types.MIXIN_RULE;}else{node['type']=types.STYLE_RULE;}}}let r$=node['rules'];if(r$){for(let i=0,l=r$.length,r;i<l&&(r=r$[i]);i++){parseCss(r,text);}}return node;}/** * conversion of sort unicode escapes with spaces like `\33 ` (and longer) into * expanded form that doesn't require trailing space `\000033` * @param {string} s * @return {string} */function _expandUnicodeEscapes(s){return s.replace(/\\([0-9a-f]{1,6})\s/gi,function(){let code=arguments[1],repeat=6-code.length;while(repeat--){code='0'+code;}return'\\'+code;});}/** * stringify parsed css. * @param {StyleNode} node * @param {boolean=} preserveProperties * @param {string=} text * @return {string} */function stringify(node,preserveProperties,text=''){// calc rule cssText let cssText='';if(node['cssText']||node['rules']){let r$=node['rules'];if(r$&&!_hasMixinRules(r$)){for(let i=0,l=r$.length,r;i<l&&(r=r$[i]);i++){cssText=stringify(r,preserveProperties,cssText);}}else{cssText=preserveProperties?node['cssText']:removeCustomProps(node['cssText']);cssText=cssText.trim();if(cssText){cssText=' '+cssText+'\n';}}}// emit rule if there is cssText if(cssText){if(node['selector']){text+=node['selector']+' '+OPEN_BRACE+'\n';}text+=cssText;if(node['selector']){text+=CLOSE_BRACE+'\n\n';}}return text;}/** * @param {Array<StyleNode>} rules * @return {boolean} */function _hasMixinRules(rules){let r=rules[0];return Boolean(r)&&Boolean(r['selector'])&&r['selector'].indexOf(VAR_START)===0;}/** * @param {string} cssText * @return {string} */function removeCustomProps(cssText){cssText=removeCustomPropAssignment(cssText);return removeCustomPropApply(cssText);}/** * @param {string} cssText * @return {string} */function removeCustomPropAssignment(cssText){return cssText.replace(RX.customProp,'').replace(RX.mixinProp,'');}/** * @param {string} cssText * @return {string} */function removeCustomPropApply(cssText){return cssText.replace(RX.mixinApply,'').replace(RX.varApply,'');}/** @enum {number} */const types={STYLE_RULE:1,KEYFRAMES_RULE:7,MEDIA_RULE:4,MIXIN_RULE:1000};const OPEN_BRACE='{';const CLOSE_BRACE='}';// helper regexp's const RX={comments:/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,port:/@import[^;]*;/gim,customProp:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,mixinProp:/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,mixinApply:/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,varApply:/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,keyframesRule:/^@[^\s]*keyframes/,multipleSpaces:/\s+/g};const VAR_START='--';const MEDIA_START='@media';const AT_START='@';var cssParse={StyleNode:StyleNode,parse:parse,stringify:stringify,removeCustomPropAssignment:removeCustomPropAssignment,types:types};/** @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 */const VAR_ASSIGN=/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi;const MIXIN_MATCH=/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi;const VAR_CONSUMED=/(--[\w-]+)\s*([:,;)]|$)/gi;const ANIMATION_MATCH=/(animation\s*:)|(animation-name\s*:)/;const MEDIA_MATCH=/@media\s(.*)/;const IS_VAR=/^--/;const BRACKETED=/\{[^}]*\}/g;const HOST_PREFIX='(?:^|[^.#[:])';const HOST_SUFFIX='($|[.:[\\s>+~])';var commonRegex={VAR_ASSIGN:VAR_ASSIGN,MIXIN_MATCH:MIXIN_MATCH,VAR_CONSUMED:VAR_CONSUMED,ANIMATION_MATCH:ANIMATION_MATCH,MEDIA_MATCH:MEDIA_MATCH,IS_VAR:IS_VAR,BRACKETED:BRACKETED,HOST_PREFIX:HOST_PREFIX,HOST_SUFFIX:HOST_SUFFIX};/** @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';/** @type {!Set<string>} */const styleTextSet=new Set();const scopingAttribute='shady-unscoped';/** * Add a specifically-marked style to the document directly, and only one copy of that style. * * @param {!HTMLStyleElement} style * @return {undefined} */function processUnscopedStyle(style){const text=style.textContent;if(!styleTextSet.has(text)){styleTextSet.add(text);const newStyle=style.cloneNode(true);document.head.appendChild(newStyle);}}/** * Check if a style is supposed to be unscoped * @param {!HTMLStyleElement} style * @return {boolean} true if the style has the unscoping attribute */function isUnscopedStyle(style){return style.hasAttribute(scopingAttribute);}var unscopedStyleHandler={scopingAttribute:scopingAttribute,processUnscopedStyle:processUnscopedStyle,isUnscopedStyle:isUnscopedStyle};/** @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';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} */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} */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 */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} */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} */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} */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 */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} */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} */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 */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 */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);}}/** * @type {function(*):*} */const wrap=window['ShadyDOM']&&window['ShadyDOM']['wrap']||(node=>node);/** * @param {Element | {is: string, extends: string}} element * @return {{is: string, typeExtension: string}} */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} */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>} */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" */function getCssBuild(element){if(cssBuild!==undefined){return(/** @type {string} */cssBuild);}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} */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} */function getBuildComment(element){const buildComment=element.localName==='template'?/** @type {!HTMLTemplateElement} */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'';}/** * Check if the css build status is optimal, and do no unneeded work. * * @param {string=} cssBuild CSS build status * @return {boolean} css build is optimal or not */function isOptimalCssBuild(cssBuild$$1=''){// CSS custom property shim always requires work if(cssBuild$$1===''||!nativeCssVariables){return false;}return nativeShadow?cssBuild$$1==='shadow':cssBuild$$1==='shady';}/** * @param {!HTMLElement} element */function removeBuildComment(element){const buildComment=element.localName==='template'?/** @type {!HTMLTemplateElement} */element.content.firstChild:element.firstChild;buildComment.parentNode.removeChild(buildComment);}var styleUtil={toCssText:toCssText,rulesForStyle:rulesForStyle,isKeyframesSelector:isKeyframesSelector,forEachRule:forEachRule,applyCss:applyCss,createScopeStyle:createScopeStyle,applyStylePlaceHolder:applyStylePlaceHolder,applyStyle:applyStyle,isTargetedBuild:isTargetedBuild,findMatchingParen:findMatchingParen,processVariableAndFallback:processVariableAndFallback,setElementClassRaw:setElementClassRaw,wrap:wrap,getIsExtends:getIsExtends,gatherStyleText:gatherStyleText,splitSelectorList:splitSelectorList,getCssBuild:getCssBuild,elementHasBuiltCss:elementHasBuiltCss,getBuildComment:getBuildComment,isOptimalCssBuild:isOptimalCssBuild};/** @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';function updateNativeProperties(element,properties){// remove previous properties for(let p in properties){// NOTE: for bc with shim, don't apply null values. if(p===null){element.style.removeProperty(p);}else{element.style.setProperty(p,properties[p]);}}}/** * @param {Element} element * @param {string} property * @return {string} */function getComputedStyleValue(element,property){/** * @const {string} */const value=window.getComputedStyle(element).getPropertyValue(property);if(!value){return'';}else{return value.trim();}}/** * return true if `cssText` contains a mixin definition or consumption * @param {string} cssText * @return {boolean} */function detectMixin(cssText){const has=MIXIN_MATCH.test(cssText)||VAR_ASSIGN.test(cssText);// reset state of the regexes MIXIN_MATCH.lastIndex=0;VAR_ASSIGN.lastIndex=0;return has;}var commonUtils={updateNativeProperties:updateNativeProperties,getComputedStyleValue:getComputedStyleValue,detectMixin:detectMixin};/** @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 */ /* * The apply shim simulates the behavior of `@apply` proposed at * https://tabatkins.github.io/specs/css-apply-rule/. * The approach is to convert a property like this: * * --foo: {color: red; background: blue;} * * to this: * * --foo_-_color: red; * --foo_-_background: blue; * * Then where `@apply --foo` is used, that is converted to: * * color: var(--foo_-_color); * background: var(--foo_-_background); * * This approach generally works but there are some issues and limitations. * Consider, for example, that somewhere *between* where `--foo` is set and used, * another element sets it to: * * --foo: { border: 2px solid red; } * * We must now ensure that the color and background from the previous setting * do not apply. This is accomplished by changing the property set to this: * * --foo_-_border: 2px solid red; * --foo_-_color: initial; * --foo_-_background: initial; * * This works but introduces one new issue. * Consider this setup at the point where the `@apply` is used: * * background: orange; * `@apply` --foo; * * In this case the background will be unset (initial) rather than the desired * `orange`. We address this by altering the property set to use a fallback * value like this: * * color: var(--foo_-_color); * background: var(--foo_-_background, orange); * border: var(--foo_-_border); * * Note that the default is retained in the property set and the `background` is * the desired `orange`. This leads us to a limitation. * * Limitation 1: * Only properties in the rule where the `@apply` * is used are considered as default values. * If another rule matches the element and sets `background` with * less specificity than the rule in which `@apply` appears, * the `background` will not be set. * * Limitation 2: * * When using Polymer's `updateStyles` api, new properties may not be set for * `@apply` properties. */'use strict';const APPLY_NAME_CLEAN=/;\s*/m;const INITIAL_INHERIT=/^\s*(initial)|(inherit)\s*$/;const IMPORTANT=/\s*!important/;// separator used between mixin-name and mixin-property-name when producing properties // NOTE: plain '-' may cause collisions in user styles const MIXIN_VAR_SEP='_-_';/** * @typedef {!Object<string, string>} */let PropertyEntry;// eslint-disable-line no-unused-vars /** * @typedef {!Object<string, boolean>} */let DependantsEntry;// eslint-disable-line no-unused-vars /** @typedef {{ * properties: PropertyEntry, * dependants: DependantsEntry * }} */let MixinMapEntry;// eslint-disable-line no-unused-vars // map of mixin to property names // --foo: {border: 2px} -> {properties: {(--foo, ['border'])}, dependants: {'element-name': proto}} class MixinMap{constructor(){/** @type {!Object<string, !MixinMapEntry>} */this._map={};}/** * @param {string} name * @param {!PropertyEntry} props */set(name,props){name=name.trim();this._map[name]={properties:props,dependants:{}};}/** * @param {string} name * @return {MixinMapEntry} */get(name){name=name.trim();return this._map[name]||null;}}/** * Callback for when an element is marked invalid * @type {?function(string)} */let invalidCallback=null;/** @unrestricted */class ApplyShim{constructor(){/** @type {?string} */this._currentElement=null;/** @type {HTMLMetaElement} */this._measureElement=null;this._map=new MixinMap();}/** * return true if `cssText` contains a mixin definition or consumption * @param {string} cssText * @return {boolean} */detectMixin(cssText){return detectMixin(cssText);}/** * Gather styles into one style for easier processing * @param {!HTMLTemplateElement} template * @return {HTMLStyleElement} */gatherStyles(template){const styleText=gatherStyleText(template.content);if(styleText){const style=/** @type {!HTMLStyleElement} */document.createElement('style');style.textContent=styleText;template.content.insertBefore(style,template.content.firstChild);return style;}return null;}/** * @param {!HTMLTemplateElement} template * @param {string} elementName * @return {StyleNode} */transformTemplate(template,elementName){if(template._gatheredStyle===undefined){template._gatheredStyle=this.gatherStyles(template);}/** @type {HTMLStyleElement} */const style=template._gatheredStyle;return style?this.transformStyle(style,elementName):null;}/** * @param {!HTMLStyleElement} style * @param {string} elementName * @return {StyleNode} */transformStyle(style,elementName=''){let ast=rulesForStyle(style);this.transformRules(ast,elementName);style.textContent=toCssText(ast);return ast;}/** * @param {!HTMLStyleElement} style * @return {StyleNode} */transformCustomStyle(style){let ast=rulesForStyle(style);forEachRule(ast,rule=>{if(rule['selector']===':root'){rule['selector']='html';}this.transformRule(rule);});style.textContent=toCssText(ast);return ast;}/** * @param {StyleNode} rules * @param {string} elementName */transformRules(rules,elementName){this._currentElement=elementName;forEachRule(rules,r=>{this.transformRule(r);});this._currentElement=null;}/** * @param {!StyleNode} rule */transformRule(rule){rule['cssText']=this.transformCssText(rule['parsedCssText'],rule);// :root was only used for variable assignment in property shim, // but generates invalid selectors with real properties. // replace with `:host > *`, which serves the same effect if(rule['selector']===':root'){rule['selector']=':host > *';}}/** * @param {string} cssText * @param {!StyleNode} rule * @return {string} */transformCssText(cssText,rule){// produce variables cssText=cssText.replace(VAR_ASSIGN,(matchText,propertyName,valueProperty,valueMixin)=>this._produceCssProperties(matchText,propertyName,valueProperty,valueMixin,rule));// consume mixins return this._consumeCssProperties(cssText,rule);}/** * @param {string} property * @return {string} */_getInitialValueForProperty(property){if(!this._measureElement){this._measureElement=/** @type {HTMLMetaElement} */document.createElement('meta');this._measureElement.setAttribute('apply-shim-measure','');this._measureElement.style.all='initial';document.head.appendChild(this._measureElement);}return window.getComputedStyle(this._measureElement).getPropertyValue(property);}/** * Walk over all rules before this rule to find fallbacks for mixins * * @param {!StyleNode} startRule * @return {!Object} */_fallbacksFromPreviousRules(startRule){// find the "top" rule let topRule=startRule;while(topRule['parent']){topRule=topRule['parent'];}const fallbacks={};let seenStartRule=false;forEachRule(topRule,r=>{// stop when we hit the input rule seenStartRule=seenStartRule||r===startRule;if(seenStartRule){return;}// NOTE: Only matching selectors are "safe" for this fallback processing // It would be prohibitive to run `matchesSelector()` on each selector, // so we cheat and only check if the same selector string is used, which // guarantees things like specificity matching if(r['selector']===startRule['selector']){Object.assign(fallbacks,this._cssTextToMap(r['parsedCssText']));}});return fallbacks;}/** * replace mixin consumption with variable consumption * @param {string} text * @param {!StyleNode=} rule * @return {string} */_consumeCssProperties(text,rule){/** @type {Array} */let m=null;// loop over text until all mixins with defintions have been applied while(m=MIXIN_MATCH.exec(text)){let matchText=m[0];let mixinName=m[1];let idx=m.index;// collect properties before apply to be "defaults" if mixin might override them // match includes a "prefix", so find the start and end positions of @apply let applyPos=idx+matchText.indexOf('@apply');let afterApplyPos=idx+matchText.length;// find props defined before this @apply let textBeforeApply=text.slice(0,applyPos);let textAfterApply=text.slice(afterApplyPos);let defaults=rule?this._fallbacksFromPreviousRules(rule):{};Object.assign(defaults,this._cssTextToMap(textBeforeApply));let replacement=this._atApplyToCssProperties(mixinName,defaults);// use regex match position to replace mixin, keep linear processing time text=`${textBeforeApply}${replacement}${textAfterApply}`;// move regex search to _after_ replacement MIXIN_MATCH.lastIndex=idx+replacement.length;}return text;}/** * produce variable consumption at the site of mixin consumption * `@apply` --foo; -> for all props (${propname}: var(--foo_-_${propname}, ${fallback[propname]}})) * Example: * border: var(--foo_-_border); padding: var(--foo_-_padding, 2px) * * @param {string} mixinName * @param {Object} fallbacks * @return {string} */_atApplyToCssProperties(mixinName,fallbacks){mixinName=mixinName.replace(APPLY_NAME_CLEAN,'');let vars=[];let mixinEntry=this._map.get(mixinName);// if we depend on a mixin before it is created // make a sentinel entry in the map to add this element as a dependency for when it is defined. if(!mixinEntry){this._map.set(mixinName,{});mixinEntry=this._map.get(mixinName);}if(mixinEntry){if(this._currentElement){mixinEntry.dependants[this._currentElement]=true;}let p,parts,f;const properties=mixinEntry.properties;for(p in properties){f=fallbacks&&fallbacks[p];parts=[p,': var(',mixinName,MIXIN_VAR_SEP,p];if(f){parts.push(',',f.replace(IMPORTANT,''));}parts.push(')');if(IMPORTANT.test(properties[p])){parts.push(' !important');}vars.push(parts.join(''));}}return vars.join('; ');}/** * @param {string} property * @param {string} value * @return {string} */_replaceInitialOrInherit(property,value){let match=INITIAL_INHERIT.exec(value);if(match){if(match[1]){// initial // replace `initial` with the concrete initial value for this property value=this._getInitialValueForProperty(property);}else{// inherit // with this purposfully illegal value, the variable will be invalid at // compute time (https://www.w3.org/TR/css-variables/#invalid-at-computed-value-time) // and for inheriting values, will behave similarly // we cannot support the same behavior for non inheriting values like 'border' value='apply-shim-inherit';}}return value;}/** * "parse" a mixin definition into a map of properties and values * cssTextToMap('border: 2px solid black') -> ('border', '2px solid black') * @param {string} text * @param {boolean=} replaceInitialOrInherit * @return {!Object<string, string>} */_cssTextToMap(text,replaceInitialOrInherit=false){let props=text.split(';');let property,value;let out={};for(let i=0,p,sp;i<props.length;i++){p=props[i];if(p){sp=p.split(':');// ignore lines that aren't definitions like @media if(sp.length>1){property=sp[0].trim();// some properties may have ':' in the value, like data urls value=sp.slice(1).join(':');if(replaceInitialOrInherit){value=this._replaceInitialOrInherit(property,value);}out[property]=value;}}}return out;}/** * @param {MixinMapEntry} mixinEntry */_invalidateMixinEntry(mixinEntry){if(!invalidCallback){return;}for(let elementName in mixinEntry.dependants){if(elementName!==this._currentElement){invalidCallback(elementName);}}}/** * @param {string} matchText * @param {string} propertyName * @param {?string} valueProperty * @param {?string} valueMixin * @param {!StyleNode} rule * @return {string} */_produceCssProperties(matchText,propertyName,valueProperty,valueMixin,rule){// handle case where property value is a mixin if(valueProperty){// form: --mixin2: var(--mixin1), where --mixin1 is in the map processVariableAndFallback(valueProperty,(prefix,value)=>{if(value&&this._map.get(value)){valueMixin=`@apply ${value};`;}});}if(!valueMixin){return matchText;}let mixinAsProperties=this._consumeCssProperties(''+valueMixin,rule);let prefix=matchText.slice(0,matchText.indexOf('--'));// `initial` and `inherit` as properties in a map should be replaced because // these keywords are eagerly evaluated when the mixin becomes CSS Custom Properties, // and would set the variable value, rather than carry the keyword to the `var()` usage. let mixinValues=this._cssTextToMap(mixinAsProperties,true);let combinedProps=mixinValues;let mixinEntry=this._map.get(propertyName);let oldProps=mixinEntry&&mixinEntry.properties;if(oldProps){// NOTE: since we use mixin, the map of properties is updated here // and this is what we want. combinedProps=Object.assign(Object.create(oldProps),mixinValues);}else{this._map.set(propertyName,combinedProps);}let out=[];let p,v;// set variables defined by current mixin let needToInvalidate=false;for(p in combinedProps){v=mixinValues[p];// if property not defined by current mixin, set initial if(v===undefined){v='initial';}if(oldProps&&!(p in oldProps)){needToInvalidate=true;}out.push(`${propertyName}${MIXIN_VAR_SEP}${p}: ${v}`);}if(needToInvalidate){this._invalidateMixinEntry(mixinEntry);}if(mixinEntry){mixinEntry.properties=combinedProps;}// because the mixinMap is global, the mixin might conflict with // a different scope's simple variable definition: // Example: // some style somewhere: // --mixin1:{ ... } // --mixin2: var(--mixin1); // some other element: // --mixin1: 10px solid red; // --foo: var(--mixin1); // In this case, we leave the original variable definition in place. if(valueProperty){prefix=`${matchText};${prefix}`;}return`${prefix}${out.join('; ')};`;}}/* exports */ /* eslint-disable no-self-assign */ApplyShim.prototype['detectMixin']=ApplyShim.prototype.detectMixin;ApplyShim.prototype['transformStyle']=ApplyShim.prototype.transformStyle;ApplyShim.prototype['transformCustomStyle']=ApplyShim.prototype.transformCustomStyle;ApplyShim.prototype['transformRules']=ApplyShim.prototype.transformRules;ApplyShim.prototype['transformRule']=ApplyShim.prototype.transformRule;ApplyShim.prototype['transformTemplate']=ApplyShim.prototype.transformTemplate;ApplyShim.prototype['_separator']=MIXIN_VAR_SEP;/* eslint-enable no-self-assign */Object.defineProperty(ApplyShim.prototype,'invalidCallback',{/** @return {?function(string)} */get(){return invalidCallback;},/** @param {?function(string)} cb */set(cb){invalidCallback=cb;}});var applyShim={default:ApplyShim};/** @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';/** * @const {!Object<string, !HTMLTemplateElement>} */const templateMap={};var templateMap$1={default:templateMap};/** @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';/* * Utilities for handling invalidating apply-shim mixins for a given template. * * The invalidation strategy involves keeping track of the "current" version of a template's mixins, and updating that count when a mixin is invalidated. * The template */ /** @const {string} */const CURRENT_VERSION='_applyShimCurrentVersion';/** @const {string} */const NEXT_VERSION='_applyShimNextVersion';/** @const {string} */const VALIDATING_VERSION='_applyShimValidatingVersion';/** * @const {Promise<void>} */const promise=Promise.resolve();/** * @param {string} elementName */function invalidate(elementName){let template=templateMap[elementName];if(template){invalidateTemplate(template);}}/** * This function can be called multiple times to mark a template invalid * and signal that the style inside must be regenerated. * * Use `startValidatingTemplate` to begin an asynchronous validation cycle. * During that cycle, call `templateIsValidating` to see if the template must * be revalidated * @param {HTMLTemplateElement} template */function invalidateTemplate(template){// default the current version to 0 template[CURRENT_VERSION]=template[CURRENT_VERSION]||0;// ensure the "validating for" flag exists template[VALIDATING_VERSION]=template[VALIDATING_VERSION]||0;// increment the next version template[NEXT_VERSION]=(template[NEXT_VERSION]||0)+1;}/** * @param {string} elementName * @return {boolean} */function isValid(elementName){let template=templateMap[elementName];if(template){return templateIsValid(template);}return true;}/** * @param {HTMLTemplateElement} template * @return {boolean} */function templateIsValid(template){return template[CURRENT_VERSION]===template[NEXT_VERSION];}/** * @param {string} elementName * @return {boolean} */function isValidating(elementName){let template=templateMap[elementName];if(template){return templateIsValidating(template);}return false;}/** * Returns true if the template is currently invalid and `startValidating` has been called since the last invalidation. * If false, the template must be validated. * @param {HTMLTemplateElement} template * @return {boolean} */function templateIsValidating(template){return!templateIsValid(template)&&template[VALIDATING_VERSION]===template[NEXT_VERSION];}/** * the template is marked as `validating` for one microtask so that all instances * found in the tree crawl of `applyStyle` will update themselves, * but the template will only be updated once. * @param {string} elementName */function startValidating(elementName){let template=templateMap[elementName];startValidatingTemplate(template);}/** * Begin an asynchronous invalidation cycle. * This should be called after every validation of a template * * After one microtask, the template will be marked as valid until the next call to `invalidateTemplate` * @param {HTMLTemplateElement} template */function startValidatingTemplate(template){// remember that the current "next version" is the reason for this validation cycle template[VALIDATING_VERSION]=template[NEXT_VERSION];// however, there only needs to be one async task to clear the counters if(!template._validating){template._validating=true;promise.then(function(){// sync the current version to let future invalidations cause a refresh cycle template[CURRENT_VERSION]=template[NEXT_VERSION];template._validating=false;});}}/** * @return {boolean} */function elementsAreInvalid(){for(let elementName in templateMap){let template=templateMap[elementName];if(!templateIsValid(template)){return true;}}return false;}var applyShimUtils={invalidate:invalidate,invalidateTemplate:invalidateTemplate,isValid:isValid,templateIsValid:templateIsValid,isValidating:isValidating,templateIsValidating:templateIsValidating,startValidating:startValidating,startValidatingTemplate:startValidatingTemplate,elementsAreInvalid:elementsAreInvalid};/** @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';/** @type {Promise<void>} */let readyPromise=null;/** @type {?function(?function())} */let whenReady=window['HTMLImports']&&window['HTMLImports']['whenReady']||null;/** @type {function()} */let resolveFn;/** * @param {?function()} callback */function documentWait(callback){requestAnimationFrame(function(){if(whenReady){whenReady(callback);}else{if(!readyPromise){readyPromise=new Promise(resolve=>{resolveFn=resolve;});if(document.readyState==='complete'){resolveFn();}else{document.addEventListener('readystatechange',()=>{if(document.readyState==='complete'){resolveFn();}});}}readyPromise.then(function(){callback&&callback();});}});}var documentWait$1={default:documentWait};/** @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';let CustomStyleProvider;const SEEN_MARKER='__seenByShadyCSS';const CACHED_STYLE='__shadyCSSCachedStyle';/** @type {?function(!HTMLStyleElement)} */let transformFn=null;/** @type {?function()} */let validateFn=null;/** This interface is provided to add document-level <style> elements to ShadyCSS for processing. These styles must be processed by ShadyCSS to simulate ShadowRoot upper-bound encapsulation from outside styles In addition, these styles may also need to be processed for @apply rules and CSS Custom Properties To add document-level styles to ShadyCSS, one can call `ShadyCSS.addDocumentStyle(styleElement)` or `ShadyCSS.addDocumentStyle({getStyle: () => styleElement})` In addition, if the process used to discover document-level styles can be synchronously flushed, one should set `ShadyCSS.documentStyleFlush`. This function will be called when calculating styles. An example usage of the document-level styling api can be found in `examples/document-style-lib.js` @unrestricted */class CustomStyleInterface{constructor(){/** @type {!Array<!CustomStyleProvider>} */this['customStyles']=[];this['enqueued']=false;// NOTE(dfreedm): use quotes here to prevent closure inlining to `function(){}`; documentWait(()=>{if(window['ShadyCSS']['flushCustomStyles']){window['ShadyCSS']['flushCustomStyles']();}});}/** * Queue a validation for new custom styles to batch style recalculations */enqueueDocumentValidation(){if(this['enqueued']||!validateFn){return;}this['enqueued']=true;documentWait(validateFn);}/** * @param {!HTMLStyleElement} style */addCustomStyle(style){if(!style[SEEN_MARKER]){style[SEEN_MARKER]=true;this['customStyles'].push(style);this.enqueueDocumentValidation();}}/** * @param {!CustomStyleProvider} customStyle * @return {HTMLStyleElement} */getStyleForCustomStyle(customStyle){if(customStyle[CACHED_STYLE]){return customStyle[CACHED_STYLE];}let style;if(customStyle['getStyle']){style=customStyle['getStyle']();}else{style=customStyle;}return style;}/** * @return {!Array<!CustomStyleProvider>} */processStyles(){const cs=this['customStyles'];for(let i=0;i<cs.length;i++){const customStyle=cs[i];if(customStyle[CACHED_STYLE]){continue;}const style=this.getStyleForCustomStyle(customStyle);if(style){// HTMLImports polyfill may have cloned the style into the main document, // which is referenced with __appliedElement. const styleToTransform=/** @type {!HTMLStyleElement} */style['__appliedElement']||style;if(transformFn){transformFn(styleToTransform);}customStyle[CACHED_STYLE]=styleToTransform;}}return cs;}}/* eslint-disable no-self-assign */CustomStyleInterface.prototype['addCustomStyle']=CustomStyleInterface.prototype.addCustomStyle;CustomStyleInterface.prototype['getStyleForCustomStyle']=CustomStyleInterface.prototype.getStyleForCustomStyle;CustomStyleInterface.prototype['processStyles']=CustomStyleInterface.prototype.processStyles;/* eslint-enable no-self-assign */Object.defineProperties(CustomStyleInterface.prototype,{'transformCallback':{/** @return {?function(!HTMLStyleElement)} */get(){return transformFn;},/** @param {?function(!HTMLStyleElement)} fn */set(fn){transformFn=fn;}},'validateCallback':{/** @return {?function()} */get(){return validateFn;},/** * @param {?function()} fn * @this {CustomStyleInterface} */set(fn){let needsEnqueue=false;if(!validateFn){needsEnqueue=true;}validateFn=fn;if(needsEnqueue){t