| Index: appengine/config_service/ui/bower_components/shadycss/src/apply-shim.js
|
| diff --git a/appengine/config_service/ui/bower_components/shadycss/src/apply-shim.js b/appengine/config_service/ui/bower_components/shadycss/src/apply-shim.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..609138de45bfa63f203e2976fd8b04bfce37e002
|
| --- /dev/null
|
| +++ b/appengine/config_service/ui/bower_components/shadycss/src/apply-shim.js
|
| @@ -0,0 +1,462 @@
|
| +/**
|
| +@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';
|
| +
|
| +import {forEachRule, processVariableAndFallback, rulesForStyle, toCssText} from './style-util.js'
|
| +import {MIXIN_MATCH, VAR_ASSIGN} from './common-regex.js'
|
| +import {detectMixin} from './common-utils.js'
|
| +import {StyleNode} from './css-parse.js' // eslint-disable-line no-unused-vars
|
| +
|
| +const APPLY_NAME_CLEAN = /;\s*/m;
|
| +const INITIAL_INHERIT = /^\s*(initial)|(inherit)\s*$/;
|
| +
|
| +// 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);
|
| + }
|
| + /**
|
| + * @param {!HTMLTemplateElement} template
|
| + * @param {string} elementName
|
| + * @return {StyleNode}
|
| + */
|
| + transformTemplate(template, elementName) {
|
| + const style = /** @type {HTMLStyleElement} */(template.content.querySelector('style'));
|
| + /** @type {StyleNode} */
|
| + let ast = null;
|
| + if (style) {
|
| + ast = this.transformStyle(style, elementName);
|
| + }
|
| + return ast;
|
| + }
|
| + /**
|
| + * @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']);
|
| + // :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
|
| + * @return {string}
|
| + */
|
| + transformCssText(cssText) {
|
| + // produce variables
|
| + cssText = cssText.replace(VAR_ASSIGN, (matchText, propertyName, valueProperty, valueMixin) =>
|
| + this._produceCssProperties(matchText, propertyName, valueProperty, valueMixin));
|
| + // consume mixins
|
| + return this._consumeCssProperties(cssText);
|
| + }
|
| + /**
|
| + * @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);
|
| + }
|
| + /**
|
| + * replace mixin consumption with variable consumption
|
| + * @param {string} text
|
| + * @return {string}
|
| + */
|
| + _consumeCssProperties(text) {
|
| + /** @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 = 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;
|
| + for (p in mixinEntry.properties) {
|
| + f = fallbacks && fallbacks[p];
|
| + parts = [p, ': var(', mixinName, MIXIN_VAR_SEP, p];
|
| + if (f) {
|
| + parts.push(',', f);
|
| + }
|
| + parts.push(')');
|
| + 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
|
| + * @return {!Object<string, string>}
|
| + */
|
| + _cssTextToMap(text) {
|
| + 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 = this._replaceInitialOrInherit(property, sp.slice(1).join(':'));
|
| + 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
|
| + * @return {string}
|
| + */
|
| + _produceCssProperties(matchText, propertyName, valueProperty, valueMixin) {
|
| + // 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);
|
| + let prefix = matchText.slice(0, matchText.indexOf('--'));
|
| + let mixinValues = this._cssTextToMap(mixinAsProperties);
|
| + 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 */
|
| +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;
|
| +Object.defineProperty(ApplyShim.prototype, 'invalidCallback', {
|
| + /** @return {?function(string)} */
|
| + get() {
|
| + return invalidCallback;
|
| + },
|
| + /** @param {?function(string)} cb */
|
| + set(cb) {
|
| + invalidCallback = cb;
|
| + }
|
| +});
|
| +
|
| +export default ApplyShim;
|
|
|