This commit is contained in:
Daniel Freedman 2016-01-19 11:50:27 -08:00
parent b1ea014529
commit 172d93cf4f
2 changed files with 567 additions and 567 deletions

View File

@ -1,275 +1,275 @@
<!--
@license
Copyright (c) 2014 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
-->
<link rel="import" href="style-util.html">
<script>
Polymer.StyleTransformer = (function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var styleUtil = Polymer.StyleUtil;
/* Transforms ShadowDOM styling into ShadyDOM styling
* scoping:
* elements in scope get scoping selector class="x-foo-scope"
* selectors re-written as follows:
div button -> div.x-foo-scope button.x-foo-scope
* :host -> scopeName
* :host(...) -> scopeName...
* ::content -> ' ' NOTE: requires use of scoping selector and selectors
cannot otherwise be scoped:
e.g. :host ::content > .bar -> x-foo > .bar
* ::shadow, /deep/: processed similar to ::content
* :host-context(...): scopeName..., ... scopeName
*/
var api = {
// Given a node and scope name, add a scoping class to each node
// in the tree. This facilitates transforming css into scoped rules.
dom: function(node, scope, useAttr, shouldRemoveScope) {
this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
},
_transformDom: function(node, selector, useAttr, shouldRemoveScope) {
if (node.setAttribute) {
this.element(node, selector, useAttr, shouldRemoveScope);
}
var c$ = Polymer.dom(node).childNodes;
for (var i=0; i<c$.length; i++) {
this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
}
},
element: function(element, scope, useAttr, shouldRemoveScope) {
if (useAttr) {
if (shouldRemoveScope) {
element.removeAttribute(SCOPE_NAME);
} else {
element.setAttribute(SCOPE_NAME, scope);
}
} else {
// note: if using classes, we add both the general 'style-scope' class
// as well as the specific scope. This enables easy filtering of all
// `style-scope` elements
if (scope) {
// note: svg on IE does not have classList so fallback to class
if (element.classList) {
if (shouldRemoveScope) {
element.classList.remove(SCOPE_NAME);
element.classList.remove(scope);
} else {
element.classList.add(SCOPE_NAME);
element.classList.add(scope);
}
} else if (element.getAttribute) {
var c = element.getAttribute(CLASS);
if (shouldRemoveScope) {
if (c) {
element.setAttribute(CLASS, c.replace(SCOPE_NAME, '')
.replace(scope, ''));
}
} else {
element.setAttribute(CLASS, c + (c ? ' ' : '') +
SCOPE_NAME + ' ' + scope);
}
}
}
}
},
elementStyles: function(element, callback) {
var styles = element._styles;
var cssText = '';
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
var rules = styleUtil.rulesForStyle(s);
cssText += nativeShadow ?
styleUtil.toCssText(rules, callback) :
this.css(rules, element.is, element.extends, callback,
element._scopeCssViaAttr) + '\n\n';
}
return cssText.trim();
},
// Given a string of cssText and a scoping string (scope), returns
// a string of scoped css where each selector is transformed to include
// a class created from the scope. ShadowDOM selectors are also transformed
// (e.g. :host) to use the scoping selector.
css: function(rules, scope, ext, callback, useAttr) {
var hostScope = this._calcHostScope(scope, ext);
scope = this._calcElementScope(scope, useAttr);
var self = this;
return styleUtil.toCssText(rules, function(rule) {
if (!rule.isScoped) {
self.rule(rule, scope, hostScope);
rule.isScoped = true;
}
if (callback) {
callback(rule, scope, hostScope);
}
});
},
_calcElementScope: function (scope, useAttr) {
if (scope) {
return useAttr ?
CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX :
CSS_CLASS_PREFIX + scope;
} else {
return '';
}
},
_calcHostScope: function(scope, ext) {
return ext ? '[is=' + scope + ']' : scope;
},
rule: function (rule, scope, hostScope) {
this._transformRule(rule, this._transformComplexSelector,
scope, hostScope);
},
// transforms a css rule to a scoped rule.
_transformRule: function(rule, transformer, scope, hostScope) {
var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
p$[i] = transformer.call(this, p, scope, hostScope);
}
// NOTE: save transformedSelector for subsequent matching of elements
// against selectors (e.g. when calculating style properties)
rule.selector = rule.transformedSelector =
p$.join(COMPLEX_SELECTOR_SEP);
},
_transformComplexSelector: function(selector, scope, hostScope) {
var stop = false;
var hostContext = false;
var self = this;
selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) {
if (!stop) {
var info = self._transformCompoundSelector(s, c, scope, hostScope);
stop = stop || info.stop;
hostContext = hostContext || info.hostContext;
c = info.combinator;
s = info.value;
} else {
s = s.replace(SCOPE_JUMP, ' ');
}
return c + s;
});
if (hostContext) {
selector = selector.replace(HOST_CONTEXT_PAREN,
function(m, pre, paren, post) {
return pre + paren + ' ' + hostScope + post +
COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
});
}
return selector;
},
_transformCompoundSelector: function(selector, combinator, scope, hostScope) {
// replace :host with host scoping class
var jumpIndex = selector.search(SCOPE_JUMP);
var hostContext = false;
if (selector.indexOf(HOST_CONTEXT) >=0) {
hostContext = true;
} else if (selector.indexOf(HOST) >=0) {
// :host(...) -> scopeName...
selector = selector.replace(HOST_PAREN, function(m, host, paren) {
return hostScope + paren;
});
// now normal :host
selector = selector.replace(HOST, hostScope);
// replace other selectors with scoping class
} else if (jumpIndex !== 0) {
selector = scope ? this._transformSimpleSelector(selector, scope) :
selector;
}
// remove left-side combinator when dealing with ::content.
if (selector.indexOf(CONTENT) >= 0) {
combinator = '';
}
// process scope jumping selectors up to the scope jump and then stop
// e.g. .zonk ::content > .foo ==> .zonk.scope > .foo
var stop;
if (jumpIndex >= 0) {
selector = selector.replace(SCOPE_JUMP, ' ');
stop = true;
}
return {value: selector, combinator: combinator, stop: stop,
hostContext: hostContext};
},
_transformSimpleSelector: function(selector, scope) {
var p$ = selector.split(PSEUDO_PREFIX);
p$[0] += scope;
return p$.join(PSEUDO_PREFIX);
},
documentRule: function(rule) {
// reset selector in case this is redone.
rule.selector = rule.parsedSelector;
this.normalizeRootSelector(rule);
if (!nativeShadow) {
this._transformRule(rule, this._transformDocumentSelector);
}
},
normalizeRootSelector: function(rule) {
if (rule.selector === ROOT) {
rule.selector = 'body';
}
},
_transformDocumentSelector: function(selector) {
return selector.match(SCOPE_JUMP) ?
this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
},
SCOPE_NAME: 'style-scope'
};
var SCOPE_NAME = api.SCOPE_NAME;
var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' +
':not(.' + SCOPE_NAME + ')';
var COMPLEX_SELECTOR_SEP = ',';
var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
var HOST = ':host';
var ROOT = ':root';
// NOTE: this supports 1 nested () pair for things like
// :host(:not([selected]), more general support requires
// parsing which seems like overkill
var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
var HOST_CONTEXT = ':host-context';
var HOST_CONTEXT_PAREN = /(.*)(?::host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
var CONTENT = '::content';
var SCOPE_JUMP = /::content|::shadow|\/deep\//;
var CSS_CLASS_PREFIX = '.';
var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
var CSS_ATTR_SUFFIX = ']';
var PSEUDO_PREFIX = ':';
var CLASS = 'class';
// exports
return api;
})();
</script>
<!--
@license
Copyright (c) 2014 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
-->
<link rel="import" href="style-util.html">
<script>
Polymer.StyleTransformer = (function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var styleUtil = Polymer.StyleUtil;
/* Transforms ShadowDOM styling into ShadyDOM styling
* scoping:
* elements in scope get scoping selector class="x-foo-scope"
* selectors re-written as follows:
div button -> div.x-foo-scope button.x-foo-scope
* :host -> scopeName
* :host(...) -> scopeName...
* ::content -> ' ' NOTE: requires use of scoping selector and selectors
cannot otherwise be scoped:
e.g. :host ::content > .bar -> x-foo > .bar
* ::shadow, /deep/: processed similar to ::content
* :host-context(...): scopeName..., ... scopeName
*/
var api = {
// Given a node and scope name, add a scoping class to each node
// in the tree. This facilitates transforming css into scoped rules.
dom: function(node, scope, useAttr, shouldRemoveScope) {
this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
},
_transformDom: function(node, selector, useAttr, shouldRemoveScope) {
if (node.setAttribute) {
this.element(node, selector, useAttr, shouldRemoveScope);
}
var c$ = Polymer.dom(node).childNodes;
for (var i=0; i<c$.length; i++) {
this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
}
},
element: function(element, scope, useAttr, shouldRemoveScope) {
if (useAttr) {
if (shouldRemoveScope) {
element.removeAttribute(SCOPE_NAME);
} else {
element.setAttribute(SCOPE_NAME, scope);
}
} else {
// note: if using classes, we add both the general 'style-scope' class
// as well as the specific scope. This enables easy filtering of all
// `style-scope` elements
if (scope) {
// note: svg on IE does not have classList so fallback to class
if (element.classList) {
if (shouldRemoveScope) {
element.classList.remove(SCOPE_NAME);
element.classList.remove(scope);
} else {
element.classList.add(SCOPE_NAME);
element.classList.add(scope);
}
} else if (element.getAttribute) {
var c = element.getAttribute(CLASS);
if (shouldRemoveScope) {
if (c) {
element.setAttribute(CLASS, c.replace(SCOPE_NAME, '')
.replace(scope, ''));
}
} else {
element.setAttribute(CLASS, (c ? c + ' ' : '') +
SCOPE_NAME + ' ' + scope);
}
}
}
}
},
elementStyles: function(element, callback) {
var styles = element._styles;
var cssText = '';
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
var rules = styleUtil.rulesForStyle(s);
cssText += nativeShadow ?
styleUtil.toCssText(rules, callback) :
this.css(rules, element.is, element.extends, callback,
element._scopeCssViaAttr) + '\n\n';
}
return cssText.trim();
},
// Given a string of cssText and a scoping string (scope), returns
// a string of scoped css where each selector is transformed to include
// a class created from the scope. ShadowDOM selectors are also transformed
// (e.g. :host) to use the scoping selector.
css: function(rules, scope, ext, callback, useAttr) {
var hostScope = this._calcHostScope(scope, ext);
scope = this._calcElementScope(scope, useAttr);
var self = this;
return styleUtil.toCssText(rules, function(rule) {
if (!rule.isScoped) {
self.rule(rule, scope, hostScope);
rule.isScoped = true;
}
if (callback) {
callback(rule, scope, hostScope);
}
});
},
_calcElementScope: function (scope, useAttr) {
if (scope) {
return useAttr ?
CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX :
CSS_CLASS_PREFIX + scope;
} else {
return '';
}
},
_calcHostScope: function(scope, ext) {
return ext ? '[is=' + scope + ']' : scope;
},
rule: function (rule, scope, hostScope) {
this._transformRule(rule, this._transformComplexSelector,
scope, hostScope);
},
// transforms a css rule to a scoped rule.
_transformRule: function(rule, transformer, scope, hostScope) {
var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
p$[i] = transformer.call(this, p, scope, hostScope);
}
// NOTE: save transformedSelector for subsequent matching of elements
// against selectors (e.g. when calculating style properties)
rule.selector = rule.transformedSelector =
p$.join(COMPLEX_SELECTOR_SEP);
},
_transformComplexSelector: function(selector, scope, hostScope) {
var stop = false;
var hostContext = false;
var self = this;
selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) {
if (!stop) {
var info = self._transformCompoundSelector(s, c, scope, hostScope);
stop = stop || info.stop;
hostContext = hostContext || info.hostContext;
c = info.combinator;
s = info.value;
} else {
s = s.replace(SCOPE_JUMP, ' ');
}
return c + s;
});
if (hostContext) {
selector = selector.replace(HOST_CONTEXT_PAREN,
function(m, pre, paren, post) {
return pre + paren + ' ' + hostScope + post +
COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
});
}
return selector;
},
_transformCompoundSelector: function(selector, combinator, scope, hostScope) {
// replace :host with host scoping class
var jumpIndex = selector.search(SCOPE_JUMP);
var hostContext = false;
if (selector.indexOf(HOST_CONTEXT) >=0) {
hostContext = true;
} else if (selector.indexOf(HOST) >=0) {
// :host(...) -> scopeName...
selector = selector.replace(HOST_PAREN, function(m, host, paren) {
return hostScope + paren;
});
// now normal :host
selector = selector.replace(HOST, hostScope);
// replace other selectors with scoping class
} else if (jumpIndex !== 0) {
selector = scope ? this._transformSimpleSelector(selector, scope) :
selector;
}
// remove left-side combinator when dealing with ::content.
if (selector.indexOf(CONTENT) >= 0) {
combinator = '';
}
// process scope jumping selectors up to the scope jump and then stop
// e.g. .zonk ::content > .foo ==> .zonk.scope > .foo
var stop;
if (jumpIndex >= 0) {
selector = selector.replace(SCOPE_JUMP, ' ');
stop = true;
}
return {value: selector, combinator: combinator, stop: stop,
hostContext: hostContext};
},
_transformSimpleSelector: function(selector, scope) {
var p$ = selector.split(PSEUDO_PREFIX);
p$[0] += scope;
return p$.join(PSEUDO_PREFIX);
},
documentRule: function(rule) {
// reset selector in case this is redone.
rule.selector = rule.parsedSelector;
this.normalizeRootSelector(rule);
if (!nativeShadow) {
this._transformRule(rule, this._transformDocumentSelector);
}
},
normalizeRootSelector: function(rule) {
if (rule.selector === ROOT) {
rule.selector = 'body';
}
},
_transformDocumentSelector: function(selector) {
return selector.match(SCOPE_JUMP) ?
this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
},
SCOPE_NAME: 'style-scope'
};
var SCOPE_NAME = api.SCOPE_NAME;
var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' +
':not(.' + SCOPE_NAME + ')';
var COMPLEX_SELECTOR_SEP = ',';
var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
var HOST = ':host';
var ROOT = ':root';
// NOTE: this supports 1 nested () pair for things like
// :host(:not([selected]), more general support requires
// parsing which seems like overkill
var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
var HOST_CONTEXT = ':host-context';
var HOST_CONTEXT_PAREN = /(.*)(?::host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
var CONTENT = '::content';
var SCOPE_JUMP = /::content|::shadow|\/deep\//;
var CSS_CLASS_PREFIX = '.';
var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
var CSS_ATTR_SUFFIX = ']';
var PSEUDO_PREFIX = ':';
var CLASS = 'class';
// exports
return api;
})();
</script>

View File

@ -1,292 +1,292 @@
<!--
@license
Copyright (c) 2014 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
-->
<link rel="import" href="../lib/style-properties.html">
<link rel="import" href="../lib/settings.html">
<link rel="import" href="../lib/style-defaults.html">
<link rel="import" href="../lib/style-cache.html">
<script>
(function() {
'use strict';
var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
var propertyUtils = Polymer.StyleProperties;
var styleTransformer = Polymer.StyleTransformer;
var styleUtil = Polymer.StyleUtil;
var styleDefaults = Polymer.StyleDefaults;
var nativeShadow = Polymer.Settings.useNativeShadow;
Polymer.Base._addFeature({
_prepStyleProperties: function() {
// note: an element should produce an x-scope stylesheet
// if it has any _stylePropertyNames
this._ownStylePropertyNames = this._styles ?
propertyUtils.decorateStyles(this._styles) :
null;
},
/**
* An element's style properties can be directly modified by
* setting key-value pairs in `customStyle` on the element
* (analogous to setting `style`) and then calling `updateStyles()`.
*
*/
customStyle: null,
/**
* Returns the computed style value for the given property.
* @param {String} property
* @return {String} the computed value
*/
getComputedStyleValue: function(property) {
return this._styleProperties && this._styleProperties[property] ||
getComputedStyle(this).getPropertyValue(property);
},
// here we have an instance time spot to put custom property data
_setupStyleProperties: function() {
this.customStyle = {};
this._styleCache = null;
this._styleProperties = null;
this._scopeSelector = null;
this._ownStyleProperties = null;
this._customStyle = null;
},
_needsStyleProperties: function() {
return Boolean(this._ownStylePropertyNames &&
this._ownStylePropertyNames.length);
},
_beforeAttached: function() {
// note: do this once automatically,
// then requires calling `updateStyles`
if (!this._scopeSelector && this._needsStyleProperties()) {
this._updateStyleProperties();
}
},
_findStyleHost: function() {
var e = this, root;
while (root = Polymer.dom(e).getOwnerRoot()) {
if (Polymer.isInstance(root.host)) {
return root.host;
}
e = root.host;
}
return styleDefaults;
},
_updateStyleProperties: function() {
var info, scope = this._findStyleHost();
// install cache in host if it doesn't exist.
if (!scope._styleCache) {
scope._styleCache = new Polymer.StyleCache();
}
var scopeData = propertyUtils
.propertyDataFromStyles(scope._styles, this);
// look in scope cache
scopeData.key.customStyle = this.customStyle;
info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
// compute style properties (fast path, if cache hit)
var scopeCached = Boolean(info);
if (scopeCached) {
// when scope cached, we can safely take style propertis out of the
// scope cache because they are only for this scope.
this._styleProperties = info._styleProperties;
} else {
this._computeStyleProperties(scopeData.properties);
}
this._computeOwnStyleProperties();
// cache miss, do work!
if (!scopeCached) {
// and look in 2ndary global cache
info = styleCache.retrieve(this.is,
this._ownStyleProperties, this._styles);
}
var globalCached = Boolean(info) && !scopeCached;
// now we have properties and a cached style if one
// is available.
var style = this._applyStyleProperties(info);
// no cache so store in cache
//console.warn(this.is, scopeCached, globalCached, info && info._scopeSelector);
if (!scopeCached) {
// create an info object for caching
// TODO(sorvell): clone style node when using native Shadow DOM
// so a style used in a root does not itself get stored in the cache
// This can lead to incorrect sharing, but should be fixed
// in `Polymer.StyleProperties.applyElementStyle`
style = style && nativeShadow ? style.cloneNode(true) : style;
info = {
style: style,
_scopeSelector: this._scopeSelector,
_styleProperties: this._styleProperties
};
scopeData.key.customStyle = {};
this.mixin(scopeData.key.customStyle, this.customStyle);
scope._styleCache.store(this.is, info, scopeData.key, this._styles);
if (!globalCached) {
// save in global cache
styleCache.store(this.is, Object.create(info), this._ownStyleProperties,
this._styles);
}
}
},
_computeStyleProperties: function(scopeProps) {
// get scope and make sure it has properties
var scope = this._findStyleHost();
// force scope to compute properties if they don't exist or if forcing
// and it doesn't need properties
if (!scope._styleProperties) {
scope._computeStyleProperties();
}
// start with scope style properties
var props = Object.create(scope._styleProperties);
// mixin own host properties (lower specifity than scope props)
this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
// mixin properties matching this element in scope
scopeProps = scopeProps ||
propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
this.mixin(props, scopeProps);
// finally mixin properties inherent to this element
this.mixin(props,
propertyUtils.scopePropertiesFromStyles(this._styles));
propertyUtils.mixinCustomStyle(props, this.customStyle);
// reify properties (note: only does own properties)
propertyUtils.reify(props);
this._styleProperties = props;
},
_computeOwnStyleProperties: function() {
var props = {};
for (var i=0, n; i < this._ownStylePropertyNames.length; i++) {
n = this._ownStylePropertyNames[i];
props[n] = this._styleProperties[n];
}
this._ownStyleProperties = props;
},
_scopeCount: 0,
_applyStyleProperties: function(info) {
// update scope selector (needed for style transformation)
var oldScopeSelector = this._scopeSelector;
// note, the scope selector is incremented per class counter
this._scopeSelector = info ? info._scopeSelector :
this.is + '-' + this.__proto__._scopeCount++;
var style = propertyUtils.applyElementStyle(this,
this._styleProperties, this._scopeSelector, info && info.style);
// apply scope selector
if (!nativeShadow) {
propertyUtils.applyElementScopeSelector(this, this._scopeSelector,
oldScopeSelector, this._scopeCssViaAttr);
}
return style;
},
serializeValueToAttribute: function(value, attribute, node) {
// override to ensure whenever classes are set, we need to shim them.
node = node || this;
if (attribute === 'class' && !nativeShadow) {
// host needed to scope styling.
// Under Shady DOM, domHost is safe to use here because we know it
// is a Polymer element
var host = node === this ? (this.domHost || this.dataHost) : this;
if (host) {
value = host._scopeElementClass(node, value);
}
}
// note: using Polymer.dom here ensures that any attribute sets
// will provoke distribution if necessary; do this iff necessary
node = (this.shadyRoot && this.shadyRoot._hasDistributed) ?
Polymer.dom(node) : node;
serializeValueToAttribute.call(this, value, attribute, node);
},
_scopeElementClass: function(element, selector) {
if (!nativeShadow && !this._scopeCssViaAttr) {
selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is +
(element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' +
element._scopeSelector : '');
}
return selector;
},
/**
* Re-evaluates and applies custom CSS properties based on dynamic
* changes to this element's scope, such as adding or removing classes
* in this element's local DOM.
*
* For performance reasons, Polymer's custom CSS property shim relies
* on this explicit signal from the user to indicate when changes have
* been made that affect the values of custom properties.
*
* @method updateStyles
* @param {Object=} properties Properties object which is mixed into
* the element's `customStyle` property. This argument provides a shortcut
* for setting `customStyle` and then calling `updateStyles`.
*/
updateStyles: function(properties) {
if (this.isAttached) {
if (properties) {
this.mixin(this.customStyle, properties);
}
// skip applying properties to self if not used
if (this._needsStyleProperties()) {
this._updateStyleProperties();
// when an element doesn't use style properties, its own properties
// should be invalidated so elements down the tree update ok.
} else {
this._styleProperties = null;
}
if (this._styleCache) {
this._styleCache.clear();
}
// always apply properties to root
this._updateRootStyles();
}
},
_updateRootStyles: function(root) {
root = root || this.root;
var c$ = Polymer.dom(root)._query(function(e) {
return e.shadyRoot || e.shadowRoot;
});
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.updateStyles) {
c.updateStyles();
}
}
}
});
/**
* Force all custom elements using cross scope custom properties,
* to update styling.
*/
Polymer.updateStyles = function(properties) {
// update default/custom styles
styleDefaults.updateStyles(properties);
// search the document for elements to update
Polymer.Base._updateRootStyles(document);
};
var styleCache = new Polymer.StyleCache();
Polymer.customStyleCache = styleCache;
var SCOPE_NAME = styleTransformer.SCOPE_NAME;
var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
})();
</script>
<!--
@license
Copyright (c) 2014 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
-->
<link rel="import" href="../lib/style-properties.html">
<link rel="import" href="../lib/settings.html">
<link rel="import" href="../lib/style-defaults.html">
<link rel="import" href="../lib/style-cache.html">
<script>
(function() {
'use strict';
var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
var propertyUtils = Polymer.StyleProperties;
var styleTransformer = Polymer.StyleTransformer;
var styleUtil = Polymer.StyleUtil;
var styleDefaults = Polymer.StyleDefaults;
var nativeShadow = Polymer.Settings.useNativeShadow;
Polymer.Base._addFeature({
_prepStyleProperties: function() {
// note: an element should produce an x-scope stylesheet
// if it has any _stylePropertyNames
this._ownStylePropertyNames = this._styles ?
propertyUtils.decorateStyles(this._styles) :
null;
},
/**
* An element's style properties can be directly modified by
* setting key-value pairs in `customStyle` on the element
* (analogous to setting `style`) and then calling `updateStyles()`.
*
*/
customStyle: null,
/**
* Returns the computed style value for the given property.
* @param {String} property
* @return {String} the computed value
*/
getComputedStyleValue: function(property) {
return this._styleProperties && this._styleProperties[property] ||
getComputedStyle(this).getPropertyValue(property);
},
// here we have an instance time spot to put custom property data
_setupStyleProperties: function() {
this.customStyle = {};
this._styleCache = null;
this._styleProperties = null;
this._scopeSelector = null;
this._ownStyleProperties = null;
this._customStyle = null;
},
_needsStyleProperties: function() {
return Boolean(this._ownStylePropertyNames &&
this._ownStylePropertyNames.length);
},
_beforeAttached: function() {
// note: do this once automatically,
// then requires calling `updateStyles`
if (!this._scopeSelector && this._needsStyleProperties()) {
this._updateStyleProperties();
}
},
_findStyleHost: function() {
var e = this, root;
while (root = Polymer.dom(e).getOwnerRoot()) {
if (Polymer.isInstance(root.host)) {
return root.host;
}
e = root.host;
}
return styleDefaults;
},
_updateStyleProperties: function() {
var info, scope = this._findStyleHost();
// install cache in host if it doesn't exist.
if (!scope._styleCache) {
scope._styleCache = new Polymer.StyleCache();
}
var scopeData = propertyUtils
.propertyDataFromStyles(scope._styles, this);
// look in scope cache
scopeData.key.customStyle = this.customStyle;
info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
// compute style properties (fast path, if cache hit)
var scopeCached = Boolean(info);
if (scopeCached) {
// when scope cached, we can safely take style propertis out of the
// scope cache because they are only for this scope.
this._styleProperties = info._styleProperties;
} else {
this._computeStyleProperties(scopeData.properties);
}
this._computeOwnStyleProperties();
// cache miss, do work!
if (!scopeCached) {
// and look in 2ndary global cache
info = styleCache.retrieve(this.is,
this._ownStyleProperties, this._styles);
}
var globalCached = Boolean(info) && !scopeCached;
// now we have properties and a cached style if one
// is available.
var style = this._applyStyleProperties(info);
// no cache so store in cache
//console.warn(this.is, scopeCached, globalCached, info && info._scopeSelector);
if (!scopeCached) {
// create an info object for caching
// TODO(sorvell): clone style node when using native Shadow DOM
// so a style used in a root does not itself get stored in the cache
// This can lead to incorrect sharing, but should be fixed
// in `Polymer.StyleProperties.applyElementStyle`
style = style && nativeShadow ? style.cloneNode(true) : style;
info = {
style: style,
_scopeSelector: this._scopeSelector,
_styleProperties: this._styleProperties
};
scopeData.key.customStyle = {};
this.mixin(scopeData.key.customStyle, this.customStyle);
scope._styleCache.store(this.is, info, scopeData.key, this._styles);
if (!globalCached) {
// save in global cache
styleCache.store(this.is, Object.create(info), this._ownStyleProperties,
this._styles);
}
}
},
_computeStyleProperties: function(scopeProps) {
// get scope and make sure it has properties
var scope = this._findStyleHost();
// force scope to compute properties if they don't exist or if forcing
// and it doesn't need properties
if (!scope._styleProperties) {
scope._computeStyleProperties();
}
// start with scope style properties
var props = Object.create(scope._styleProperties);
// mixin own host properties (lower specifity than scope props)
this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
// mixin properties matching this element in scope
scopeProps = scopeProps ||
propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
this.mixin(props, scopeProps);
// finally mixin properties inherent to this element
this.mixin(props,
propertyUtils.scopePropertiesFromStyles(this._styles));
propertyUtils.mixinCustomStyle(props, this.customStyle);
// reify properties (note: only does own properties)
propertyUtils.reify(props);
this._styleProperties = props;
},
_computeOwnStyleProperties: function() {
var props = {};
for (var i=0, n; i < this._ownStylePropertyNames.length; i++) {
n = this._ownStylePropertyNames[i];
props[n] = this._styleProperties[n];
}
this._ownStyleProperties = props;
},
_scopeCount: 0,
_applyStyleProperties: function(info) {
// update scope selector (needed for style transformation)
var oldScopeSelector = this._scopeSelector;
// note, the scope selector is incremented per class counter
this._scopeSelector = info ? info._scopeSelector :
this.is + '-' + this.__proto__._scopeCount++;
var style = propertyUtils.applyElementStyle(this,
this._styleProperties, this._scopeSelector, info && info.style);
// apply scope selector
if (!nativeShadow) {
propertyUtils.applyElementScopeSelector(this, this._scopeSelector,
oldScopeSelector, this._scopeCssViaAttr);
}
return style;
},
serializeValueToAttribute: function(value, attribute, node) {
// override to ensure whenever classes are set, we need to shim them.
node = node || this;
if (attribute === 'class' && !nativeShadow) {
// host needed to scope styling.
// Under Shady DOM, domHost is safe to use here because we know it
// is a Polymer element
var host = node === this ? (this.domHost || this.dataHost) : this;
if (host) {
value = host._scopeElementClass(node, value);
}
}
// note: using Polymer.dom here ensures that any attribute sets
// will provoke distribution if necessary; do this iff necessary
node = (this.shadyRoot && this.shadyRoot._hasDistributed) ?
Polymer.dom(node) : node;
serializeValueToAttribute.call(this, value, attribute, node);
},
_scopeElementClass: function(element, selector) {
if (!nativeShadow && !this._scopeCssViaAttr) {
selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is +
(element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' +
element._scopeSelector : '');
}
return selector;
},
/**
* Re-evaluates and applies custom CSS properties based on dynamic
* changes to this element's scope, such as adding or removing classes
* in this element's local DOM.
*
* For performance reasons, Polymer's custom CSS property shim relies
* on this explicit signal from the user to indicate when changes have
* been made that affect the values of custom properties.
*
* @method updateStyles
* @param {Object=} properties Properties object which is mixed into
* the element's `customStyle` property. This argument provides a shortcut
* for setting `customStyle` and then calling `updateStyles`.
*/
updateStyles: function(properties) {
if (this.isAttached) {
if (properties) {
this.mixin(this.customStyle, properties);
}
// skip applying properties to self if not used
if (this._needsStyleProperties()) {
this._updateStyleProperties();
// when an element doesn't use style properties, its own properties
// should be invalidated so elements down the tree update ok.
} else {
this._styleProperties = null;
}
if (this._styleCache) {
this._styleCache.clear();
}
// always apply properties to root
this._updateRootStyles();
}
},
_updateRootStyles: function(root) {
root = root || this.root;
var c$ = Polymer.dom(root)._query(function(e) {
return e.shadyRoot || e.shadowRoot;
});
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.updateStyles) {
c.updateStyles();
}
}
}
});
/**
* Force all custom elements using cross scope custom properties,
* to update styling.
*/
Polymer.updateStyles = function(properties) {
// update default/custom styles
styleDefaults.updateStyles(properties);
// search the document for elements to update
Polymer.Base._updateRootStyles(document);
};
var styleCache = new Polymer.StyleCache();
Polymer.customStyleCache = styleCache;
var SCOPE_NAME = styleTransformer.SCOPE_NAME;
var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
})();
</script>