Non-destructive @keyframes rule transformation.

Previously, the transformer did not disambiguate selectors in `@media`
blocks and keyframes in `@keyframes` blocks. Now, the transformer can
safely transform `@keyframes` blocks. Before a selector is transformed,
if the selector has a parent, it is checked. If the checked parent is a
`@keyframes` rule, the selector transformation is skipped.

Element-specific `@keyframes` are suffixed with the scoped element name.
For example, `@keyframes foo` in an element scoped with `x-el-0` will by
transformed to `@keyframes foo-x-el-0`. References to that animation in
the element's local styles will be updated as well.

Added tests for the new keyframes transformation.
This commit is contained in:
Chris Joel 2015-12-04 17:06:41 -08:00
parent 4a9ef8e96d
commit b9f2482eb5
11 changed files with 747 additions and 291 deletions

View File

@ -73,6 +73,8 @@ Polymer.CssParse = (function() {
node.type = this.types.MEDIA_RULE; node.type = this.types.MEDIA_RULE;
} else if (s.match(this._rx.keyframesRule)) { } else if (s.match(this._rx.keyframesRule)) {
node.type = this.types.KEYFRAMES_RULE; node.type = this.types.KEYFRAMES_RULE;
node.keyframesName =
node.selector.split(this._rx.multipleSpaces).pop();
} }
} else { } else {
if (s.indexOf(this.VAR_START) === 0) { if (s.indexOf(this.VAR_START) === 0) {

View File

@ -139,7 +139,7 @@ Note, all features of `custom-style` are available when defining styles as part
e.textContent; e.textContent;
} }
if (e.textContent) { if (e.textContent) {
styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function(rule) { styleUtil.forEachRule(styleUtil.rulesForStyle(e), function(rule) {
styleTransformer.documentRule(rule); styleTransformer.documentRule(rule);
}); });
// Allow all custom-styles defined in this turn to register // Allow all custom-styles defined in this turn to register

View File

@ -16,7 +16,7 @@ Polymer.StyleExtends = (function() {
var styleUtil = Polymer.StyleUtil; var styleUtil = Polymer.StyleUtil;
return { return {
hasExtends: function(cssText) { hasExtends: function(cssText) {
return Boolean(cssText.match(this.rx.EXTEND)); return Boolean(cssText.match(this.rx.EXTEND));
}, },
@ -24,7 +24,7 @@ Polymer.StyleExtends = (function() {
transform: function(style) { transform: function(style) {
var rules = styleUtil.rulesForStyle(style); var rules = styleUtil.rulesForStyle(style);
var self = this; var self = this;
styleUtil.forEachStyleRule(rules, function(rule) { styleUtil.forEachRule(rules, function(rule) {
var map = self._mapRule(rule); var map = self._mapRule(rule);
if (rule.parent) { if (rule.parent) {
var m; var m;
@ -72,7 +72,7 @@ Polymer.StyleExtends = (function() {
// TODO: this misses `%foo, .bar` as an unetended selector but // TODO: this misses `%foo, .bar` as an unetended selector but
// this seems rare and could possibly be unsupported. // this seems rare and could possibly be unsupported.
source.selector = source.selector.replace(this.rx.STRIP, ''); source.selector = source.selector.replace(this.rx.STRIP, '');
source.selector = (source.selector && source.selector + ',\n') + source.selector = (source.selector && source.selector + ',\n') +
target.selector; target.selector;
if (source.extends) { if (source.extends) {
source.extends.forEach(function(e) { source.extends.forEach(function(e) {

View File

@ -25,11 +25,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// decorates styles with rule info and returns an array of used style // decorates styles with rule info and returns an array of used style
// property names // property names
decorateStyles: function(styles) { decorateStyles: function(styles) {
var self = this, props = {}; var self = this, props = {}, keyframes = [];
styleUtil.forRulesInStyles(styles, function(rule) { styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule); self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
}, function onKeyframesRule(rule) {
keyframes.push(rule);
}); });
// Cache all found keyframes rules for later reference:
styles._keyframes = keyframes;
// return this list of property names *consumes* in these styles. // return this list of property names *consumes* in these styles.
var names = []; var names = [];
for (var i in props) { for (var i in props) {
@ -87,7 +91,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var parts = cssText.split(';'); var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) { for (var i=0, p; i<parts.length; i++) {
p = parts[i]; p = parts[i];
if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) { if (p.match(this.rx.MIXIN_MATCH) ||
p.match(this.rx.VAR_MATCH) ||
this.rx.ANIMATION_MATCH.test(p) ||
styleUtil.isKeyframesSelector(rule)) {
customCssText += p + ';\n'; customCssText += p + ';\n';
} }
} }
@ -181,6 +188,46 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
rule.cssText = output; rule.cssText = output;
}, },
// Apply keyframe transformations to the cssText of a given rule. The
// keyframeTransforms object is a map of keyframe names to transformer
// functions which take in cssText and spit out transformed cssText.
applyKeyframeTransforms: function(rule, keyframeTransforms) {
var input = rule.cssText;
var output = rule.cssText;
if (rule.hasAnimations == null) {
// Cache whether or not the rule has any animations to begin with:
rule.hasAnimations = this.rx.ANIMATION_MATCH.test(input);
}
// If there are no animations referenced, we can skip transforms:
if (rule.hasAnimations) {
var transform;
// If we haven't transformed this rule before, we iterate over all
// transforms:
if (rule.keyframeNamesToTransform == null) {
rule.keyframeNamesToTransform = [];
for (var keyframe in keyframeTransforms) {
transform = keyframeTransforms[keyframe];
output = transform(input);
// If the transform actually changed the CSS text, we cache the
// transform name for future use:
if (input !== output) {
input = output;
rule.keyframeNamesToTransform.push(keyframe);
}
}
} else {
// If we already have a list of keyframe names that apply to this
// rule, we apply only those keyframe name transforms:
for (var i = 0; i < rule.keyframeNamesToTransform.length; ++i) {
transform = keyframeTransforms[rule.keyframeNamesToTransform[i]];
input = transform(input);
}
output = input;
}
}
rule.cssText = output;
},
// Test if the rules in these styles matches the given `element` and if so, // Test if the rules in these styles matches the given `element` and if so,
// collect any custom properties into `props`. // collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) { propertyDataFromStyles: function(styles, element) {
@ -256,15 +303,60 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
hostSelector; hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX); this.rx.HOST_SUFFIX);
var keyframeTransforms =
this._elementKeyframeTransforms(element, scopeSelector);
return styleTransformer.elementStyles(element, function(rule) { return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties); self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) { if (!nativeShadow &&
!Polymer.StyleUtil.isKeyframesSelector(rule) &&
rule.cssText) {
// NOTE: keyframe transforms only scope munge animation names, so it
// is not necessary to apply them in ShadowDOM.
self.applyKeyframeTransforms(rule, keyframeTransforms);
self._scopeSelector(rule, hostRx, hostSelector, self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector); element._scopeCssViaAttr, scopeSelector);
} }
}); });
}, },
_elementKeyframeTransforms: function(element, scopeSelector) {
var keyframesRules = element._styles._keyframes;
var keyframeTransforms = {};
if (!nativeShadow) {
// For non-ShadowDOM, we transform all known keyframes rules in
// advance for the current scope. This allows us to catch keyframes
// rules that appear anywhere in the stylesheet:
for (var i = 0, keyframesRule = keyframesRules[i];
i < keyframesRules.length;
keyframesRule = keyframesRules[++i]) {
this._scopeKeyframes(keyframesRule, scopeSelector);
keyframeTransforms[keyframesRule.keyframesName] =
this._keyframesRuleTransformer(keyframesRule);
}
}
return keyframeTransforms;
},
// Generate a factory for transforming a chunk of CSS text to handle a
// particular scoped keyframes rule.
_keyframesRuleTransformer: function(keyframesRule) {
return function(cssText) {
return cssText.replace(
keyframesRule.keyframesNameRx,
keyframesRule.transformedKeyframesName);
};
},
// Transforms `@keyframes` names to be unique for the current host.
// Example: @keyframes foo-anim -> @keyframes foo-anim-x-foo-0
_scopeKeyframes: function(rule, scopeId) {
rule.keyframesNameRx = new RegExp(rule.keyframesName, 'g');
rule.transformedKeyframesName = rule.keyframesName + '-' + scopeId;
rule.transformedSelector = rule.transformedSelector || rule.selector;
rule.selector = rule.transformedSelector.replace(
rule.keyframesName, rule.transformedKeyframesName);
},
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes): // 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 // non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide // host selector: x-foo.wide -> x-foo.x-foo-42.wide
@ -359,6 +451,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// var(--a, fallback-literal(with-one-nested-parentheses)) // var(--a, fallback-literal(with-one-nested-parentheses))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi, VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi, VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
ANIMATION_MATCH: /(animation\s*:)|(animation-name\s*:)/,
IS_VAR: /^--/, IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g, BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])', HOST_PREFIX: '(?:^|[^.#[:])',

View File

@ -148,8 +148,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// transforms a css rule to a scoped rule. // transforms a css rule to a scoped rule.
_transformRule: function(rule, transformer, scope, hostScope) { _transformRule: function(rule, transformer, scope, hostScope) {
var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { // we want to skip transformation of rules that appear in keyframes,
p$[i] = transformer.call(this, p, scope, hostScope); // because they are keyframe selectors, not element selectors.
if (!styleUtil.isKeyframesSelector(rule)) {
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 // NOTE: save transformedSelector for subsequent matching of elements
// against selectors (e.g. when calculating style properties) // against selectors (e.g. when calculating style properties)

View File

@ -24,15 +24,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
rules = this.parser.parse(rules); rules = this.parser.parse(rules);
} }
if (callback) { if (callback) {
this.forEachStyleRule(rules, callback); this.forEachRule(rules, callback);
} }
return this.parser.stringify(rules, preserveProperties); return this.parser.stringify(rules, preserveProperties);
}, },
forRulesInStyles: function(styles, callback) { forRulesInStyles: function(styles, styleRuleCallback, keyframesRuleCallback) {
if (styles) { if (styles) {
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
this.forEachStyleRule(this.rulesForStyle(s), callback); this.forEachRule(
this.rulesForStyle(s),
styleRuleCallback,
keyframesRuleCallback);
} }
} }
}, },
@ -44,21 +47,31 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return style.__cssRules; return style.__cssRules;
}, },
forEachStyleRule: function(node, callback) { // Tests if a rule is a keyframes selector, which looks almost exactly
// like a normal selector but is not (it has nothing to do with scoping
// for example).
isKeyframesSelector: function(rule) {
return rule.parent &&
rule.parent.type === this.ruleTypes.KEYFRAMES_RULE;
},
forEachRule: function(node, styleRuleCallback, keyframesRuleCallback) {
if (!node) { if (!node) {
return; return;
} }
var skipRules = false; var skipRules = false;
if (node.type === this.ruleTypes.STYLE_RULE) { if (node.type === this.ruleTypes.STYLE_RULE) {
callback(node); styleRuleCallback(node);
} else if (node.type === this.ruleTypes.KEYFRAMES_RULE || } else if (keyframesRuleCallback &&
node.type === this.ruleTypes.MIXIN_RULE) { node.type === this.ruleTypes.KEYFRAMES_RULE) {
keyframesRuleCallback(node);
} else if (node.type === this.ruleTypes.MIXIN_RULE) {
skipRules = true; skipRules = true;
} }
var r$ = node.rules; var r$ = node.rules;
if (r$ && !skipRules) { if (r$ && !skipRules) {
for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
this.forEachStyleRule(r, callback); this.forEachRule(r, styleRuleCallback, keyframesRuleCallback);
} }
} }
}, },

60
test/smoke/keyframes.html Normal file
View File

@ -0,0 +1,60 @@
<!doctype html>
<!--
@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
-->
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
<body>
<style is="custom-style">
:root {
--color: blue;
--anim-color: red;
}
.alternative {
--color: green;
--anim-color: blue;
}
</style>
<dom-module id="test-keyframes">
<template><style>
:host {
display: block;
color: var(--color);
animation: foo 3s;
height: 20px;
}
@keyframes foo {
0% {
background: var(--anim-color);
}
100% {
background: yellow;
}
}
</style><content></content></template>
<script>
Polymer({
is: 'test-keyframes'
});
</script>
</dom-module>
<p>Text should be the color blue. Background should animate from the color red to the color yellow, and then become transparent.</p>
<test-keyframes>red</test-keyframes>
<p>Text should be the color green. Background should animate from the color blue to the color yellow, and then become transparent.</p>
<test-keyframes class="alternative">blue</test-keyframes>
</body>

View File

@ -25,7 +25,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
:host { :host {
display: block; display: block;
} }
#story-card .story-content { #story-card .story-content {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 1.2em; font-size: 1.2em;
@ -55,7 +55,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
background: url(http://www.google.com/icon.png); background: url(http://www.google.com/icon.png);
}; };
} }
#mixin1 { #mixin1 {
@apply(--mixin1); @apply(--mixin1);
} }
@ -95,6 +95,67 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</script> </script>
</dom-module> </dom-module>
<dom-module id="x-keyframes">
<template>
<style>
:host {
display: block;
position: relative;
border: 10px solid blue;
left: 0px;
/* Prefix required by Safari <= 8 */
-webkit-animation-duration: 0.3s;
animation-duration: 0.3s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
:host([animated]) {
/* Prefix required by Safari <= 8 */
-webkit-animation-name: x-keyframes-animation;
animation-name: x-keyframes-animation;
}
/* Prefix required by Safari <= 8 */
@-webkit-keyframes x-keyframes-animation {
0% {
left: var(--c1);
}
100% {
left: var(--c2);
@apply(--keyframe-finish);
}
}
@keyframes x-keyframes-animation {
0% {
left: var(--c1);
}
100% {
left: var(--c2);
@apply(--keyframe-finish);
}
}
</style>
x-keyframes
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-keyframes',
properties: {
animated: {
type: Boolean,
value: false,
reflectToAttribute: true
}
}
});
});
</script>
</dom-module>
<dom-module id="x-scope"> <dom-module id="x-scope">
<style> <style>
:host { :host {
@ -165,6 +226,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
@apply(--mixin5); @apply(--mixin5);
} }
#keyframes2 {
--keyframe-finish: {
left: 20px;
};
}
x-child-scope { x-child-scope {
padding: 10px; padding: 10px;
} }
@ -189,6 +256,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<div id="mixin4">mixin4</div> <div id="mixin4">mixin4</div>
<div id="mixin5">mixin5</div> <div id="mixin5">mixin5</div>
<hr> <hr>
<x-keyframes id="keyframes1"></x-keyframes>
<x-keyframes id="keyframes2"></x-keyframes>
<x-child-scope id="child"></x-child-scope> <x-child-scope id="child"></x-child-scope>
<story-card id="card"></story-card> <story-card id="card"></story-card>
<div id="override">override</div> <div id="override">override</div>
@ -267,6 +336,31 @@ suite('scoped-styling-apply', function() {
assertComputed(styled.$.child.$.mixin7, '17px'); assertComputed(styled.$.child.$.mixin7, '17px');
}); });
test('mixins apply to @keyframe rules', function(done) {
var xKeyframes1 = styled.$.keyframes1;
var xKeyframes2 = styled.$.keyframes2;
var completed = 0;
[xKeyframes1, xKeyframes2].forEach(function(xKeyframes, index) {
var target = index === 0 ? '10px' : '20px';
var onAnimationEnd = function() {
assert.include(xKeyframes.getComputedStyleValue('left'), target);
xKeyframes.removeEventListener('animationend', onAnimationEnd);
xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = false;
if (++completed > 1) {
done();
}
};
xKeyframes.addEventListener('animationend', onAnimationEnd);
xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = true;
});
});
// TODO(sorvell): fix for #1761 was reverted; include test once this issue is addressed // TODO(sorvell): fix for #1761 was reverted; include test once this issue is addressed
// test('mixin values can be overridden by subsequent concrete properties', function() { // test('mixin values can be overridden by subsequent concrete properties', function() {
// assertComputed(styled.$.override, '19px'); // assertComputed(styled.$.override, '19px');

View File

@ -23,27 +23,26 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<x-scope></x-scope> <x-scope></x-scope>
<dom-module id="x-grand-child-scope"> <dom-module id="x-grand-child-scope">
<style>
:host {
display: block;
padding: 8px;
}
#scope {
border: var(--scope-var);
}
#child {
border: var(--child-scope-var);
}
#me {
border: var(--grand-child-scope-var);
}
</style>
<template> <template>
<style>
:host {
display: block;
padding: 8px;
}
#scope {
border: var(--scope-var);
}
#child {
border: var(--child-scope-var);
}
#me {
border: var(--grand-child-scope-var);
}
</style>
<div id="me">x-grand-child-scope</div> <div id="me">x-grand-child-scope</div>
<div id="scope">From x-scope</div> <div id="scope">From x-scope</div>
<div id="child">From x-child-scope</div> <div id="child">From x-child-scope</div>
@ -56,15 +55,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-host-property"> <dom-module id="x-host-property">
<style>
:host {
display: block;
padding: 8px;
border: var(--scope-var);
}
</style>
<template> <template>
<style>
:host {
display: block;
padding: 8px;
border: var(--scope-var);
}
</style>
Host property Host property
</template> </template>
<script> <script>
@ -75,31 +73,30 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-child-scope"> <dom-module id="x-child-scope">
<style>
:host {
display: block;
padding: 8px;
}
:root {
--gc4-scope: 5px solid green;
}
#me {
border: var(--child-scope-var);
}
#gc2 {
--grand-child-scope-var: 4px solid seagreen;
}
#gc4 {
--grand-child-scope-var:
var(--gc4-scope);
}
</style>
<template> <template>
<style>
:host {
display: block;
padding: 8px;
}
:root {
--gc4-scope: 5px solid green;
}
#me {
border: var(--child-scope-var);
}
#gc2 {
--grand-child-scope-var: 4px solid seagreen;
}
#gc4 {
--grand-child-scope-var:
var(--gc4-scope);
}
</style>
<div id="me">x-child-scope</div> <div id="me">x-child-scope</div>
<x-grand-child-scope id="gc1"></x-grand-child-scope> <x-grand-child-scope id="gc1"></x-grand-child-scope>
<x-grand-child-scope id="gc2"></x-grand-child-scope> <x-grand-child-scope id="gc2"></x-grand-child-scope>
@ -114,17 +111,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-overrides"> <dom-module id="x-overrides">
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
--grand-child-scope-var: var(--rename);
}
</style>
<template> <template>
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
--grand-child-scope-var: var(--rename);
}
</style>
overrides: overrides:
<x-grand-child-scope id="gc1"></x-grand-child-scope> <x-grand-child-scope id="gc1"></x-grand-child-scope>
</template> </template>
@ -136,20 +132,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-overrides2"> <dom-module id="x-overrides2">
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
}
:root {
--grand-child-scope-var: var(--rename);
}
</style>
<template> <template>
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
}
:root {
--grand-child-scope-var: var(--rename);
}
</style>
overrides: overrides:
<x-grand-child-scope id="gc1"></x-grand-child-scope> <x-grand-child-scope id="gc1"></x-grand-child-scope>
</template> </template>
@ -161,14 +156,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-late"> <dom-module id="x-late">
<style>
:host {
border: var(--late);
display: block;
}
</style>
<template> <template>
<style>
:host {
border: var(--late);
display: block;
}
</style>
late late
</template> </template>
<script> <script>
@ -179,20 +173,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-overrides3"> <dom-module id="x-overrides3">
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
}
:root {
--fillin: 16px;
}
</style>
<template> <template>
<style>
:host {
border: 1px dashed gray;
margin: 8px;
padding: 8px;
display: block;
}
:root {
--fillin: 16px;
}
</style>
overrides: overrides:
<x-late id="late"></x-late> <x-late id="late"></x-late>
</template> </template>
@ -204,16 +197,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-has-def"> <dom-module id="x-has-def">
<style>
:host {
border: var(--border, --defaultBorder);
margin: 8px;
padding: 8px;
display: block;
}
</style>
<template> <template>
<style>
:host {
border: var(--border, --defaultBorder);
margin: 8px;
padding: 8px;
display: block;
}
</style>
Element with default variable. Element with default variable.
</template> </template>
<script> <script>
@ -224,13 +216,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-has-if"> <dom-module id="x-has-if">
<style>
.iffy {
border: var(--scope-var);
}
</style>
<template> <template>
<style>
.iffy {
border: var(--scope-var);
}
</style>
<template is="dom-if" if="{{gogo}}"> <template is="dom-if" if="{{gogo}}">
<div class="iffy">iffy</div> <div class="iffy">iffy</div>
</template> </template>
@ -248,13 +239,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-button"> <dom-module id="x-button">
<style>
:host {
display: block;
border: var(--button-border);
}
</style>
<template> <template>
<style>
:host {
display: block;
border: var(--button-border);
}
</style>
Button! Button!
</template> </template>
<script> <script>
@ -268,14 +259,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-dynamic"> <dom-module id="x-dynamic">
<style>
:host {
display: block;
margin: 20px;
border: var(--dynamic);
}
</style>
<template> <template>
<style>
:host {
display: block;
margin: 20px;
border: var(--dynamic);
}
</style>
Dynamic Dynamic
</template> </template>
<script> <script>
@ -287,113 +278,177 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</script> </script>
</dom-module> </dom-module>
<dom-module id="x-scope"> <dom-module id="x-keyframes">
<style>
:host {
x--invalid: 15px solid gray;
display: block;
padding: 8px;
--scope-var: 1px solid black;
--fallback: 7px solid orange;
--default1: var(--undefined, 6px solid yellow);
--default2: var(--undefined, --fallback);
--default3: var(--undefined, rgb(128, 200, 250));
--defaultBorder: 22px solid green;
--a: 10px;
--b: 5px;
--primary-color: rgb(128, 128, 128);
--late: var(--fillin);
--button-border: 16px solid tomato;
--after: 17px solid brown;
--end-term: 19px solid blue}
:root{--ws-term: 18px solid orange}
#me {
border: var(--scope-var);
}
x-child-scope {
--child-scope-var: 2px solid orange;
--grand-child-scope-var: 3px solid steelblue;
}
x-child-scope.special {
--child-scope-var: 12px solid orange;
}
#applyDefault1 {
border: var(--undefined, 6px solid yellow);
}
#applyDefault2 {
border: var(--undefined, --fallback);
}
#default1 {
border: var(--default1);
}
#default2 {
border: var(--default2);
}
#default3 {
padding: 8px;
background-color: var(--default3);
}
#defaultElement2 {
--defaultBorder: 23px solid goldenrod;
}
#overrides1a, #overrides1b, #overrides2 {
--rename: 8px solid navy;
}
#overrides1b, #overrides2 {
--grand-child-scope-var: 9px solid orange;
}
#overridesConcrete {
border: var(--scope-var);
border: 4px solid steelblue;
}
#calc {
border: solid red;
border-width: calc(var(--a) + var(--b));
}
#shadow {
box-shadow: 10px 10px 10px var(--primary-color);
-webkit-box-shadow: 10px 10px 10px var(--primary-color);
}
x-host-property {
border: 10px solid purple;
}
#invalid {
border: var(--invalid);
}
#after::after {
content: 'after';
border: var(--after);
}
#wsTerm {
border: var(--ws-term)
}
#endTerm {border: var(--end-term)}
</style>
<template> <template>
<style>
:host {
display: block;
position: relative;
border: 10px solid blue;
left: 0px;
/* Prefix required by Safari <= 8 */
-webkit-animation-duration: 0.3s;
animation-duration: 0.3s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
:host([animated]) {
/* Prefix required by Safari <= 8 */
-webkit-animation-name: x-keyframes-animation;
animation-name: x-keyframes-animation;
}
/* Prefix required by Safari <= 8 */
@-webkit-keyframes x-keyframes-animation {
0% {
left: var(--a);
}
100% {
left: var(--b);
}
}
@keyframes x-keyframes-animation {
0% {
left: var(--a);
}
100% {
left: var(--b);
}
}
</style>
x-keyframes
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-keyframes',
properties: {
animated: {
type: Boolean,
value: false,
reflectToAttribute: true
}
}
});
});
</script>
</dom-module>
<dom-module id="x-scope">
<template>
<style>
:host {
x--invalid: 15px solid gray;
display: block;
padding: 8px;
--scope-var: 1px solid black;
--fallback: 7px solid orange;
--default1: var(--undefined, 6px solid yellow);
--default2: var(--undefined, --fallback);
--default3: var(--undefined, rgb(128, 200, 250));
--defaultBorder: 22px solid green;
--a: 10px;
--b: 5px;
--primary-color: rgb(128, 128, 128);
--late: var(--fillin);
--button-border: 16px solid tomato;
--after: 17px solid brown;
--end-term: 19px solid blue}
:root{--ws-term: 18px solid orange}
#me {
border: var(--scope-var);
}
x-child-scope {
--child-scope-var: 2px solid orange;
--grand-child-scope-var: 3px solid steelblue;
}
x-child-scope.special {
--child-scope-var: 12px solid orange;
}
#applyDefault1 {
border: var(--undefined, 6px solid yellow);
}
#applyDefault2 {
border: var(--undefined, --fallback);
}
#default1 {
border: var(--default1);
}
#default2 {
border: var(--default2);
}
#default3 {
padding: 8px;
background-color: var(--default3);
}
#defaultElement2 {
--defaultBorder: 23px solid goldenrod;
}
#overrides1a, #overrides1b, #overrides2 {
--rename: 8px solid navy;
}
#overrides1b, #overrides2 {
--grand-child-scope-var: 9px solid orange;
}
#overridesConcrete {
border: var(--scope-var);
border: 4px solid steelblue;
}
#calc {
border: solid red;
border-width: calc(var(--a) + var(--b));
}
#shadow {
box-shadow: 10px 10px 10px var(--primary-color);
-webkit-box-shadow: 10px 10px 10px var(--primary-color);
}
x-host-property {
border: 10px solid purple;
}
#invalid {
border: var(--invalid);
}
#after::after {
content: 'after';
border: var(--after);
}
#wsTerm {
border: var(--ws-term)
}
x-keyframes:nth-of-type(2) {
--b: -5px;
}
#endTerm {border: var(--end-term)}
</style>
<div id="me">x-scope</div> <div id="me">x-scope</div>
<x-keyframes id="keyframes"></x-keyframes>
<x-keyframes id="keyframes2"></x-keyframes>
<x-child-scope id="child"></x-child-scope> <x-child-scope id="child"></x-child-scope>
<x-child-scope id="child2"></x-child-scope> <x-child-scope id="child2"></x-child-scope>
<x-overrides id="overrides1a"></x-overrides> <x-overrides id="overrides1a"></x-overrides>
@ -429,50 +484,49 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module> </dom-module>
<dom-module id="x-inside"> <dom-module id="x-inside">
<style> <template>
:host { <style>
display: inline-block; :host {
border: var(--border) solid orange; display: inline-block;
height: 10px; border: var(--border) solid orange;
width: 10px; height: 10px;
background-color: tomato; width: 10px;
} background-color: tomato;
</style> }
<template> </style>
</template>
</template> <script>
<script> HTMLImports.whenReady(function() {
HTMLImports.whenReady(function() { Polymer({
Polymer({ is: 'x-inside'
is: 'x-inside' });
}); });
}); </script>
</script> </dom-module>
</dom-module>
<dom-module id="simple-element">
<template>
<style>
:host {
display: block;
}
<dom-module id="simple-element"> x-inside {
<style> color: var(--dne);
:host { --border: 10px;
display: block; }
} </style>
<x-inside id="inner"></x-inside>
x-inside { </template>
color: var(--dne); <script>
--border: 10px; HTMLImports.whenReady(function() {
} Polymer({
</style> is: 'simple-element'
<template> });
<x-inside id="inner"></x-inside>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'simple-element'
}); });
}); </script>
</script> </dom-module>
</dom-module>
<style> <style>
.variable-override { .variable-override {
@ -517,6 +571,46 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var styled = document.querySelector('x-scope'); var styled = document.querySelector('x-scope');
test('variables in @keyframes', function(done) {
var xKeyframes = styled.$.keyframes;
var onAnimationEnd = function() {
assertStylePropertyValue(xKeyframes, 'left', '5px');
xKeyframes.removeEventListener('animationend', onAnimationEnd);
xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = false;
done();
};
assertStylePropertyValue(xKeyframes, '--a', '10px');
assertStylePropertyValue(xKeyframes, '--b', '5px');
xKeyframes.addEventListener('animationend', onAnimationEnd);
xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = true;
});
test('instances of scoped @keyframes', function(done) {
var xKeyframes = styled.$.keyframes2;
var onAnimationEnd = function() {
assertStylePropertyValue(xKeyframes, 'left', '5px');
xKeyframes.removeEventListener('animationend', onAnimationEnd);
xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = false;
done();
};
assertStylePropertyValue(xKeyframes, '--a', '10px');
assertStylePropertyValue(xKeyframes, '--b', '-5px');
xKeyframes.addEventListener('animationend', onAnimationEnd);
xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = true;
});
test('mutiple elements in document', function() { test('mutiple elements in document', function() {
var e$ = document.querySelectorAll('simple-element'); var e$ = document.querySelectorAll('simple-element');
assertComputed(e$[0].$.inner, '10px'); assertComputed(e$[0].$.inner, '10px');

View File

@ -1,3 +1,59 @@
<dom-module id="x-keyframes">
<template>
<style>
:host {
display: block;
position: relative;
border: 10px solid blue;
left: 0px;
/* Prefix required by Safari <= 8 */
-webkit-animation-duration: 0.3s;
animation-duration: 0.3s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
:host([animated]) {
/* Prefix required by Safari <= 8 */
-webkit-animation-name: x-keyframes-animation;
animation-name: x-keyframes-animation;
}
/* Prefix required by Safari <= 8 */
@-webkit-keyframes x-keyframes-animation {
0% {
left: var(--keyframes0, 0px);
}
100% {
left: var(--keyframes100, 10px);
}
}
@keyframes x-keyframes-animation {
0% {
left: var(--keyframes0, 0px);
}
100% {
left: var(--keyframes100, 10px);
}
}
</style>
x-keyframes
</template>
<script>
Polymer({
is: 'x-keyframes',
properties: {
animated: {
type: Boolean,
value: false,
reflectToAttribute: true
}
}
});
</script>
</dom-module>
<dom-module id="x-gchild"> <dom-module id="x-gchild">
<template> <template>
<!-- styles can be in templates --> <!-- styles can be in templates -->
@ -70,12 +126,17 @@
:host { :host {
display: block; display: block;
border: 1px solid orange; border: 1px solid orange;
--keyframes100: 100px;
} }
:host(.wide) { :host(.wide) {
border-width: 2px; border-width: 2px;
} }
#keyframes2.special {
--keyframes100: 200px;
}
#simple { #simple {
border: 3px solid orange; border: 3px solid orange;
} }
@ -169,6 +230,8 @@
<circle id="circle" cx="12" cy="12" r="10"></circle> <circle id="circle" cx="12" cy="12" r="10"></circle>
</svg> </svg>
<x-scope-class id="scopeClass"></x-scope-class> <x-scope-class id="scopeClass"></x-scope-class>
<x-keyframes id="keyframes"></x-keyframes>
<x-keyframes id="keyframes2"></x-keyframes>
</template> </template>
</dom-module> </dom-module>
<script> <script>

View File

@ -164,6 +164,38 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assertComputed(d, '0px'); assertComputed(d, '0px');
}); });
test('keyframes change scope', function(done) {
var xKeyframes = styled.$.keyframes;
var onAnimationEnd = function() {
xKeyframes.removeEventListener('animationend', onAnimationEnd);
xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
assertComputed(xKeyframes, '100px', 'left');
xKeyframes = styled.$.keyframes2;
onAnimationEnd = function() {
xKeyframes.removeEventListener('animationend', onAnimationEnd);
xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
assertComputed(xKeyframes, '200px', 'left');
done();
};
xKeyframes.addEventListener('animationend', onAnimationEnd);
xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);
Polymer.dom(xKeyframes).classList.add('special');
xKeyframes.updateStyles();
xKeyframes.animated = true;
};
xKeyframes.addEventListener('animationend', onAnimationEnd);
xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);
xKeyframes.animated = true;
assertComputed(xKeyframes, '0px', 'left');
});
test('elements with computed classes', function() { test('elements with computed classes', function() {
assertComputed(styled.$.computed, '0px'); assertComputed(styled.$.computed, '0px');
styled.aClass = 'computed'; styled.aClass = 'computed';
@ -218,10 +250,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
test('styles shimmed in registration order', function() { test('styles shimmed in registration order', function() {
var s$ = document.head.querySelectorAll('style[scope]'); var s$ = document.head.querySelectorAll('style[scope]');
var expected = ['x-gchild', 'x-child2', 'x-styled', 'x-button', 'x-mixed-case', var expected = ['x-keyframes', 'x-keyframes-1', 'x-keyframes-0', 'x-gchild', 'x-child2',
'x-mixed-case-button', 'x-dynamic-scope', 'x-dynamic-template', 'x-styled', 'x-button', 'x-mixed-case', 'x-mixed-case-button',
'x-dynamic-svg', 'x-specificity', 'x-overriding', 'x-dynamic-scope', 'x-dynamic-template', 'x-dynamic-svg',
'x-overriding-0', 'x-specificity-parent-0', 'x-specificity-nested-0']; 'x-specificity', 'x-overriding', 'x-overriding-0',
'x-specificity-parent-0', 'x-specificity-nested-0'];
var actual = []; var actual = [];
for (var i=0; i<s$.length; i++) { for (var i=0; i<s$.length; i++) {
actual.push(s$[i].getAttribute('scope')); actual.push(s$[i].getAttribute('scope'));