Merge pull request #3310 from Polymer/fix-svg-scope

Fix svg scope
This commit is contained in:
Kevin Schaaf 2016-01-19 12:05:30 -08:00
commit 55b91b3db7
4 changed files with 948 additions and 947 deletions

View File

@ -1,380 +1,380 @@
<!--
@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-transformer.html">
<script>
Polymer.StyleProperties = (function() {
'use strict';
var nativeShadow = Polymer.Settings.useNativeShadow;
var matchesSelector = Polymer.DomApi.matchesSelector;
var styleUtil = Polymer.StyleUtil;
var styleTransformer = Polymer.StyleTransformer;
return {
// decorates styles with rule info and returns an array of used style
// property names
decorateStyles: function(styles) {
var self = this, props = {};
styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
});
// return this list of property names *consumes* in these styles.
var names = [];
for (var i in props) {
names.push(i);
}
return names;
},
// decorate a single rule with property info
decorateRule: function(rule) {
if (rule.propertyInfo) {
return rule.propertyInfo;
}
var info = {}, properties = {};
var hasProperties = this.collectProperties(rule, properties);
if (hasProperties) {
info.properties = properties;
// TODO(sorvell): workaround parser seeing mixins as additional rules
rule.rules = null;
}
info.cssText = this.collectCssText(rule);
rule.propertyInfo = info;
return info;
},
// collects the custom properties from a rule's cssText
collectProperties: function(rule, properties) {
var info = rule.propertyInfo;
if (info) {
if (info.properties) {
Polymer.Base.mixin(properties, info.properties);
return true;
}
} else {
var m, rx = this.rx.VAR_ASSIGN;
var cssText = rule.parsedCssText;
var any;
while (m = rx.exec(cssText)) {
// note: group 2 is var, 3 is mixin
properties[m[1]] = (m[2] || m[3]).trim();
any = true;
}
return any;
}
},
// returns cssText of properties that consume variables/mixins
collectCssText: function(rule) {
var customCssText = '';
var cssText = rule.parsedCssText;
// NOTE: we support consumption inside mixin assignment
// but not production, so strip out {...}
cssText = cssText.replace(this.rx.BRACKETED, '')
.replace(this.rx.VAR_ASSIGN, '');
var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) {
p = parts[i];
customCssText += p + ';\n';
}
return customCssText;
},
collectPropertiesInCssText: function(cssText, props) {
var m;
while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
props[m[1]] = true;
var def = m[2];
if (def && def.match(this.rx.IS_VAR)) {
props[def] = true;
}
}
},
// turns custom properties into realized values.
reify: function(props) {
// big perf optimization here: reify only *own* properties
// since this object has __proto__ of the element's scope properties
var names = Object.getOwnPropertyNames(props);
for (var i=0, n; i < names.length; i++) {
n = names[i];
props[n] = this.valueForProperty(props[n], props);
}
},
// given a property value, returns the reified value
// a property value may be:
// (1) a literal value like: red or 5px;
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b);
// (3) a literal mixin value like { properties }. Each of these properties
// can have values that are: (a) literal, (b) variables, (c) @apply mixins.
valueForProperty: function(property, props) {
// case (1) default
// case (3) defines a mixin and we have to reify the internals
if (property) {
if (property.indexOf(';') >=0) {
property = this.valueForProperties(property, props);
} else {
// case (2) variable
var self = this;
var fn = function(all, prefix, value, fallback) {
var propertyValue = (self.valueForProperty(props[value], props) ||
(props[fallback] ?
self.valueForProperty(props[fallback], props) :
fallback));
return prefix + (propertyValue || '');
};
property = property.replace(this.rx.VAR_MATCH, fn);
}
}
return property && property.trim() || '';
},
// note: we do not yet support mixin within mixin
valueForProperties: function(property, props) {
var parts = property.split(';');
for (var i=0, p, m; i<parts.length; i++) {
if (p = parts[i]) {
m = p.match(this.rx.MIXIN_MATCH);
if (m) {
p = this.valueForProperty(props[m[1]], props);
} else {
var pp = p.split(':');
if (pp[1]) {
pp[1] = pp[1].trim();
pp[1] = this.valueForProperty(pp[1], props) || pp[1];
}
p = pp.join(':');
}
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
// strip trailing ;
p.slice(0, -1) :
p || '';
}
}
return parts.join(';');
},
applyProperties: function(rule, props) {
var output = '';
// dynamically added sheets may not be decorated so ensure they are.
if (!rule.propertyInfo) {
this.decorateRule(rule);
}
if (rule.propertyInfo.cssText) {
output = this.valueForProperties(rule.propertyInfo.cssText, props);
}
rule.cssText = output;
},
// Test if the rules in these styles matches the given `element` and if so,
// collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) {
var props = {}, self = this;
// generates a unique key for these matches
var o = [], i = 0;
styleUtil.forRulesInStyles(styles, function(rule) {
// TODO(sorvell): we could trim the set of rules at declaration
// time to only include ones that have properties
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
// match element against transformedSelector: selector may contain
// unwanted uniquification and parsedSelector does not directly match
// for :host selectors.
if (element && rule.propertyInfo.properties &&
matchesSelector.call(element, rule.transformedSelector
|| rule.parsedSelector)) {
self.collectProperties(rule, props);
// produce numeric key for these matches for lookup
addToBitMask(i, o);
}
i++;
});
return {properties: props, key: o};
},
// Test if a rule matches scope criteria (* or :root) and if so,
// collect any custom properties into `props`.
scopePropertiesFromStyles: function(styles) {
if (!styles._scopeStyleProperties) {
styles._scopeStyleProperties =
this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
}
return styles._scopeStyleProperties;
},
// Test if a rule matches host criteria (:host) and if so,
// collect any custom properties into `props`.
//
// TODO(sorvell): this should change to collecting properties from any
// :host(...) and then matching these against self.
hostPropertiesFromStyles: function(styles) {
if (!styles._hostStyleProperties) {
styles._hostStyleProperties =
this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
}
return styles._hostStyleProperties;
},
selectedPropertiesFromStyles: function(styles, selectors) {
var props = {}, self = this;
styleUtil.forRulesInStyles(styles, function(rule) {
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
for (var i=0; i < selectors.length; i++) {
if (rule.parsedSelector === selectors[i]) {
self.collectProperties(rule, props);
return;
}
}
});
return props;
},
transformStyles: function(element, properties, scopeSelector) {
var self = this;
var hostSelector = styleTransformer
._calcHostScope(element.is, element.extends);
var rxHostSelector = element.extends ?
'\\' + hostSelector.slice(0, -1) + '\\]' :
hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX);
return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) {
self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector);
}
});
},
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide
_scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) {
rule.transformedSelector = rule.transformedSelector || rule.selector;
var selector = rule.transformedSelector;
var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' +
scopeId + ']' :
'.' + scopeId;
var parts = selector.split(',');
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
parts[i] = p.match(hostRx) ?
p.replace(hostSelector, hostSelector + scope) :
scope + ' ' + p;
}
rule.selector = parts.join(',');
},
applyElementScopeSelector: function(element, selector, old, viaAttr) {
var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) :
element.className;
var v = old ? c.replace(old, selector) :
(c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
if (c !== v) {
if (viaAttr) {
element.setAttribute(styleTransformer.SCOPE_NAME, v);
} else {
element.className = v;
}
}
},
applyElementStyle: function(element, properties, selector, style) {
// calculate cssText to apply
var cssText = style ? style.textContent || '' :
this.transformStyles(element, properties, selector);
// if shady and we have a cached style that is not style, decrement
var s = element._customStyle;
if (s && !nativeShadow && (s !== style)) {
s._useCount--;
if (s._useCount <= 0 && s.parentNode) {
s.parentNode.removeChild(s);
}
}
// apply styling always under native or if we generated style
// or the cached style is not in document(!)
if (nativeShadow || (!style || !style.parentNode)) {
// update existing style only under native
if (nativeShadow && element._customStyle) {
element._customStyle.textContent = cssText;
style = element._customStyle;
// otherwise, if we have css to apply, do so
} else if (cssText) {
// apply css after the scope style of the element to help with
// style precedence rules.
style = styleUtil.applyCss(cssText, selector,
nativeShadow ? element.root : null, element._scopeStyle);
}
}
// ensure this style is our custom style and increment its use count.
if (style) {
style._useCount = style._useCount || 0;
// increment use count if we changed styles
if (element._customStyle != style) {
style._useCount++;
}
element._customStyle = style;
}
return style;
},
// customStyle properties are applied if they are truthy or 0. Otherwise,
// they are skipped; this allows properties previously set in customStyle
// to be easily reset to inherited values.
mixinCustomStyle: function(props, customStyle) {
var v;
for (var i in customStyle) {
v = customStyle[i];
if (v || v === 0) {
props[i] = v;
}
}
},
rx: {
VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi,
MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
// note, this supports:
// var(--a)
// var(--a, --b)
// var(--a, fallback-literal)
// var(--a, fallback-literal(with-one-nested-parentheses))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])',
HOST_SUFFIX: '($|[.:[\\s>+~])'
},
HOST_SELECTORS: [':host'],
SCOPE_SELECTORS: [':root'],
XSCOPE_NAME: 'x-scope'
};
function addToBitMask(n, bits) {
var o = parseInt(n / 32);
var v = 1 << (n % 32);
bits[o] = (bits[o] || 0) | v;
}
})();
</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-transformer.html">
<script>
Polymer.StyleProperties = (function() {
'use strict';
var nativeShadow = Polymer.Settings.useNativeShadow;
var matchesSelector = Polymer.DomApi.matchesSelector;
var styleUtil = Polymer.StyleUtil;
var styleTransformer = Polymer.StyleTransformer;
return {
// decorates styles with rule info and returns an array of used style
// property names
decorateStyles: function(styles) {
var self = this, props = {};
styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
});
// return this list of property names *consumes* in these styles.
var names = [];
for (var i in props) {
names.push(i);
}
return names;
},
// decorate a single rule with property info
decorateRule: function(rule) {
if (rule.propertyInfo) {
return rule.propertyInfo;
}
var info = {}, properties = {};
var hasProperties = this.collectProperties(rule, properties);
if (hasProperties) {
info.properties = properties;
// TODO(sorvell): workaround parser seeing mixins as additional rules
rule.rules = null;
}
info.cssText = this.collectCssText(rule);
rule.propertyInfo = info;
return info;
},
// collects the custom properties from a rule's cssText
collectProperties: function(rule, properties) {
var info = rule.propertyInfo;
if (info) {
if (info.properties) {
Polymer.Base.mixin(properties, info.properties);
return true;
}
} else {
var m, rx = this.rx.VAR_ASSIGN;
var cssText = rule.parsedCssText;
var any;
while (m = rx.exec(cssText)) {
// note: group 2 is var, 3 is mixin
properties[m[1]] = (m[2] || m[3]).trim();
any = true;
}
return any;
}
},
// returns cssText of properties that consume variables/mixins
collectCssText: function(rule) {
var customCssText = '';
var cssText = rule.parsedCssText;
// NOTE: we support consumption inside mixin assignment
// but not production, so strip out {...}
cssText = cssText.replace(this.rx.BRACKETED, '')
.replace(this.rx.VAR_ASSIGN, '');
var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) {
p = parts[i];
customCssText += p + ';\n';
}
return customCssText;
},
collectPropertiesInCssText: function(cssText, props) {
var m;
while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
props[m[1]] = true;
var def = m[2];
if (def && def.match(this.rx.IS_VAR)) {
props[def] = true;
}
}
},
// turns custom properties into realized values.
reify: function(props) {
// big perf optimization here: reify only *own* properties
// since this object has __proto__ of the element's scope properties
var names = Object.getOwnPropertyNames(props);
for (var i=0, n; i < names.length; i++) {
n = names[i];
props[n] = this.valueForProperty(props[n], props);
}
},
// given a property value, returns the reified value
// a property value may be:
// (1) a literal value like: red or 5px;
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b);
// (3) a literal mixin value like { properties }. Each of these properties
// can have values that are: (a) literal, (b) variables, (c) @apply mixins.
valueForProperty: function(property, props) {
// case (1) default
// case (3) defines a mixin and we have to reify the internals
if (property) {
if (property.indexOf(';') >=0) {
property = this.valueForProperties(property, props);
} else {
// case (2) variable
var self = this;
var fn = function(all, prefix, value, fallback) {
var propertyValue = (self.valueForProperty(props[value], props) ||
(props[fallback] ?
self.valueForProperty(props[fallback], props) :
fallback));
return prefix + (propertyValue || '');
};
property = property.replace(this.rx.VAR_MATCH, fn);
}
}
return property && property.trim() || '';
},
// note: we do not yet support mixin within mixin
valueForProperties: function(property, props) {
var parts = property.split(';');
for (var i=0, p, m; i<parts.length; i++) {
if (p = parts[i]) {
m = p.match(this.rx.MIXIN_MATCH);
if (m) {
p = this.valueForProperty(props[m[1]], props);
} else {
var pp = p.split(':');
if (pp[1]) {
pp[1] = pp[1].trim();
pp[1] = this.valueForProperty(pp[1], props) || pp[1];
}
p = pp.join(':');
}
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
// strip trailing ;
p.slice(0, -1) :
p || '';
}
}
return parts.join(';');
},
applyProperties: function(rule, props) {
var output = '';
// dynamically added sheets may not be decorated so ensure they are.
if (!rule.propertyInfo) {
this.decorateRule(rule);
}
if (rule.propertyInfo.cssText) {
output = this.valueForProperties(rule.propertyInfo.cssText, props);
}
rule.cssText = output;
},
// Test if the rules in these styles matches the given `element` and if so,
// collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) {
var props = {}, self = this;
// generates a unique key for these matches
var o = [], i = 0;
styleUtil.forRulesInStyles(styles, function(rule) {
// TODO(sorvell): we could trim the set of rules at declaration
// time to only include ones that have properties
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
// match element against transformedSelector: selector may contain
// unwanted uniquification and parsedSelector does not directly match
// for :host selectors.
if (element && rule.propertyInfo.properties &&
matchesSelector.call(element, rule.transformedSelector
|| rule.parsedSelector)) {
self.collectProperties(rule, props);
// produce numeric key for these matches for lookup
addToBitMask(i, o);
}
i++;
});
return {properties: props, key: o};
},
// Test if a rule matches scope criteria (* or :root) and if so,
// collect any custom properties into `props`.
scopePropertiesFromStyles: function(styles) {
if (!styles._scopeStyleProperties) {
styles._scopeStyleProperties =
this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
}
return styles._scopeStyleProperties;
},
// Test if a rule matches host criteria (:host) and if so,
// collect any custom properties into `props`.
//
// TODO(sorvell): this should change to collecting properties from any
// :host(...) and then matching these against self.
hostPropertiesFromStyles: function(styles) {
if (!styles._hostStyleProperties) {
styles._hostStyleProperties =
this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
}
return styles._hostStyleProperties;
},
selectedPropertiesFromStyles: function(styles, selectors) {
var props = {}, self = this;
styleUtil.forRulesInStyles(styles, function(rule) {
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
for (var i=0; i < selectors.length; i++) {
if (rule.parsedSelector === selectors[i]) {
self.collectProperties(rule, props);
return;
}
}
});
return props;
},
transformStyles: function(element, properties, scopeSelector) {
var self = this;
var hostSelector = styleTransformer
._calcHostScope(element.is, element.extends);
var rxHostSelector = element.extends ?
'\\' + hostSelector.slice(0, -1) + '\\]' :
hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX);
return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) {
self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector);
}
});
},
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide
_scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) {
rule.transformedSelector = rule.transformedSelector || rule.selector;
var selector = rule.transformedSelector;
var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' +
scopeId + ']' :
'.' + scopeId;
var parts = selector.split(',');
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
parts[i] = p.match(hostRx) ?
p.replace(hostSelector, hostSelector + scope) :
scope + ' ' + p;
}
rule.selector = parts.join(',');
},
applyElementScopeSelector: function(element, selector, old, viaAttr) {
var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) :
(element.getAttribute('class') || '');
var v = old ? c.replace(old, selector) :
(c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
if (c !== v) {
if (viaAttr) {
element.setAttribute(styleTransformer.SCOPE_NAME, v);
} else {
element.setAttribute('class', v);
}
}
},
applyElementStyle: function(element, properties, selector, style) {
// calculate cssText to apply
var cssText = style ? style.textContent || '' :
this.transformStyles(element, properties, selector);
// if shady and we have a cached style that is not style, decrement
var s = element._customStyle;
if (s && !nativeShadow && (s !== style)) {
s._useCount--;
if (s._useCount <= 0 && s.parentNode) {
s.parentNode.removeChild(s);
}
}
// apply styling always under native or if we generated style
// or the cached style is not in document(!)
if (nativeShadow || (!style || !style.parentNode)) {
// update existing style only under native
if (nativeShadow && element._customStyle) {
element._customStyle.textContent = cssText;
style = element._customStyle;
// otherwise, if we have css to apply, do so
} else if (cssText) {
// apply css after the scope style of the element to help with
// style precedence rules.
style = styleUtil.applyCss(cssText, selector,
nativeShadow ? element.root : null, element._scopeStyle);
}
}
// ensure this style is our custom style and increment its use count.
if (style) {
style._useCount = style._useCount || 0;
// increment use count if we changed styles
if (element._customStyle != style) {
style._useCount++;
}
element._customStyle = style;
}
return style;
},
// customStyle properties are applied if they are truthy or 0. Otherwise,
// they are skipped; this allows properties previously set in customStyle
// to be easily reset to inherited values.
mixinCustomStyle: function(props, customStyle) {
var v;
for (var i in customStyle) {
v = customStyle[i];
if (v || v === 0) {
props[i] = v;
}
}
},
rx: {
VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi,
MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
// note, this supports:
// var(--a)
// var(--a, --b)
// var(--a, fallback-literal)
// var(--a, fallback-literal(with-one-nested-parentheses))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])',
HOST_SUFFIX: '($|[.:[\\s>+~])'
},
HOST_SELECTORS: [':host'],
SCOPE_SELECTORS: [':root'],
XSCOPE_NAME: 'x-scope'
};
function addToBitMask(n, bits) {
var o = parseInt(n / 32);
var v = 1 << (n % 32);
bits[o] = (bits[o] || 0) | v;
}
})();
</script>

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>

View File

@ -231,6 +231,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
test('svg elements properly scoped', function() {
assert.include(styled.$.circle.getAttribute('class'), 'style-scope x-styled');
assert.notInclude(styled.$.circle.getAttribute('class'), 'null');
assertComputed(styled.$.circle, '1px', 'strokeWidth');
});