Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Unified Diff: appengine/config_service/ui/bower_components/shadycss/src/apply-shim.js

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;

Powered by Google App Engine
This is Rietveld 408576698