Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shim CSS Mixins in terms of CSS Custom Properties #3587

Merged
merged 80 commits into from
Jun 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
17bc75a
check for native css variables
dfreedm Mar 9, 2016
ff5658a
keep custom properties, still unpack mixins
dfreedm Mar 10, 2016
5ea78b0
make `updateStyles` (modulo @apply) work with native custom properties.
Mar 11, 2016
b2777f9
modifications
dfreedm Mar 11, 2016
0374478
sigh
dfreedm Mar 11, 2016
c2b5a82
fix tests in firefox safari and chrome 49
dfreedm Mar 12, 2016
bc982c8
add back missing styleutil code in x-styling
dfreedm Mar 12, 2016
b4ec339
support all the fallback cases with nested parens
dfreedm Mar 14, 2016
6589a50
fix rules with property and mixin definitions for native
dfreedm Mar 14, 2016
49e0873
First draft of @apply shim
dfreedm Mar 16, 2016
5d700e5
step 3
dfreedm Mar 17, 2016
62dc8b9
Process stylesheets in custom-styles
dfreedm Mar 21, 2016
79afddf
always add semicolon in flattenMixin
dfreedm Mar 22, 2016
c120d37
reset order back to master
dfreedm Mar 22, 2016
0e903bc
step 4
dfreedm Mar 22, 2016
b351a99
Temporary ordering fixup for native @apply shim.
Mar 22, 2016
8f9d308
better step 5
dfreedm Mar 22, 2016
e72e974
make custom-style support @apply shim.
Mar 22, 2016
45fd0ec
* make @apply regex match globally
Mar 22, 2016
626083c
Collect default property values between consumption and application
dfreedm Mar 22, 2016
142c846
stupid stateful regexes
dfreedm Mar 23, 2016
e62b322
Handle realiasing mixins (step 6)
dfreedm Mar 23, 2016
4a21057
Add more tests for apply shim
dfreedm Mar 23, 2016
844b9e7
fix :root for apply shim
dfreedm Mar 24, 2016
fcf96dd
add StyleTransformer smoke test.
Mar 30, 2016
96d341a
Move regexes from style-properties to style-util for easier sharing
dfreedm Mar 31, 2016
e287c6f
Move check for CSS Custom Properties to settings lib
dfreedm Mar 31, 2016
fb0eaaa
add apply shim to smoke test
dfreedm Mar 31, 2016
39b067f
actually let uses set useNativeCSSProperties false
dfreedm Apr 14, 2016
6eca881
SCOPE_SELECTORS needs to work with built selectors for custom-style a…
dfreedm Apr 14, 2016
a96210c
No need to decorate styles for apply shim
dfreedm Apr 18, 2016
fc7ea29
apply mixins and gather defaults incrementally
dfreedm Apr 19, 2016
558a3f7
Fix @apply consumption to incrementally process rule text
dfreedm Apr 19, 2016
1dc1ccb
MORE APPLY SHIM TESTING
dfreedm Apr 19, 2016
f279858
Make sure @apply without parens works in property shim as expected
dfreedm Apr 20, 2016
49e1c9f
Simpler tracking of mixin properties
dfreedm Apr 21, 2016
b4ead5c
Support builds of CSS
dfreedm Apr 22, 2016
8274efc
patch a few spots that custom property shim needs to know about the b…
dfreedm Apr 22, 2016
b9c4da2
still need to transform the selectors if a shadow build was the source
dfreedm Apr 22, 2016
788c5e4
support tests with build to choose the right property
dfreedm Apr 23, 2016
7a7fbfa
make `importHref` avoid re-importing already loaded resources.
Apr 23, 2016
3f27aeb
One more spot a shady build will break custom property shim
dfreedm Apr 25, 2016
795831f
A few more allowances for the builds
dfreedm Apr 26, 2016
4a3b322
mark elements and custom-styles as built, no globals
dfreedm Apr 28, 2016
4fcf6c2
use propertyDataFromStyles for :host and :root
dfreedm Apr 28, 2016
0f5acbb
make tests pass by hacking `propertyDataFromStyles`, needs refactoring.
Apr 28, 2016
7afa3cf
Search for properties in :host and :root rules at the same time
dfreedm Apr 29, 2016
53f8d2e
Don't expect shady built styles to be in head
dfreedm May 2, 2016
9d272e0
Fixes #3637. Normalizes attached timing between Shady and Shadow DOM …
May 6, 2016
00bb79f
Fixes #3638. Avoid spamming document.head with already loaded link el…
May 6, 2016
b67f413
actually listen for the error event (unclear how to test error withou…
May 6, 2016
2d37b2b
fix typo
May 6, 2016
f17685b
Merge branch 'fix-3637,3638' into mixins-as-custom-properties
May 6, 2016
053bc8a
fix lint errors
dfreedm May 23, 2016
b4c853c
Merge branch 'master' into mixins-as-custom-properties
dfreedm May 24, 2016
f16f6ab
fix bad merge conflict
dfreedm May 24, 2016
4f92704
Safari 9.1.1 is still busted, drop minor version check for AppleWebKit
dfreedm May 24, 2016
0a7cd88
only apply statically shimmed styles if the element has cssText (this…
May 25, 2016
a33c687
* slight optimization: cache cssBuild info on element.
May 25, 2016
6e1bff0
revert dom scoping change and add clarifying comment about why this i…
May 25, 2016
03fd748
formatting
May 26, 2016
72f194c
custom-style: avoid applying shimmed custom properties when native cu…
May 26, 2016
c946232
avoid shimming styles under shady dom when there is a shady css build.
May 26, 2016
7644af2
custom-style: when native custom properties are in use and no build i…
May 26, 2016
1be473d
avoid work in the presence of a css build.
May 31, 2016
0d14881
Merge branch 'master' into mixins-as-custom-properties
May 31, 2016
6cc7774
add test for :host(element-name)
May 31, 2016
c597fff
correct custom-style under shadow build when using shady and custom p…
May 31, 2016
4500e4b
fix tests to not rely on order in className
May 31, 2016
eccaee2
Support custom-style with css-build status in HTMLImports polyfill
dfreedm Jun 1, 2016
ce06254
Clean up logic in custom-style _apply
dfreedm Jun 3, 2016
a2550bc
Loop over all property names ever used for a mixin
dfreedm Jun 3, 2016
acf5f5a
Bail early if rule does not have properties
dfreedm Jun 3, 2016
5626fc7
Comments.
Jun 3, 2016
b3d6336
test that invalid @media rules do *not* apply via the custom properti…
Jun 3, 2016
4be2598
Use `_-_` as seperator for apply-shim created variables
dfreedm Jun 3, 2016
22af46f
Fix custom-style test with new separator
dfreedm Jun 4, 2016
6383358
fix a few more tests for built styles
dfreedm Jun 4, 2016
0ae2ee7
Revert "Fixes #3637. Normalizes attached timing between Shady and Sha…
dfreedm Jun 7, 2016
1ec23a5
[ci skip] PolymerBuild global has been removed
dfreedm Jun 7, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions src/lib/apply-shim.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<!--
@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>
/**
* The apply shim simulates the behavior of `@apply` proposed at
* https://tabatkins.github.io/specs/css-apply-rule/.
* The approach is to convert a property like this:
*
* --foo: {color: red; background: blue;}
*
* to this:
*
* --foo_-_color: red;
* --foo_-_background: blue;
*
* Then where `@apply --foo` is used, that is converted to:
*
* color: var(--foo_-_color);
* background: var(--foo_-_background);
*
* This approach generally works but there are some issues and limitations.
* Consider, for example, that somewhere *between* where `--foo` is set and used,
* another element sets it to:
*
* --foo: { border: 2px solid red; }
*
* We must now ensure that the color and background from the previous setting
* do not apply. This is accomplished by changing the property set to this:
*
* --foo_-_border: 2px solid red;
* --foo_-_color: initial;
* --foo_-_background: initial;
*
* This works but introduces one new issue.
* Consider this setup at the point where the `@apply` is used:
*
* background: orange;
* @apply --foo;
*
* In this case the background will be unset (initial) rather than the desired
* `orange`. We address this by altering the property set to use a fallback
* value like this:
*
* color: var(--foo_-_color);
* background: var(--foo_-_background, orange);
* border: var(--foo_-_border);
*
* Note that the default is retained in the property set and the `background` is
* the desired `orange`. This leads us to a limitation.
*
* Limitation 1:

* Only properties in the rule where the `@apply`
* is used are considered as default values.
* If another rule matches the element and sets `background` with
* less specificity than the rule in which `@apply` appears,
* the `background` will not be set.
*
* Limitation 2:
*
* When using Polymer's `updateStyles` api, new properties may not be set for
* `@apply` properties.

*/
Polymer.ApplyShim = (function(){
'use strict';

var styleUtil = Polymer.StyleUtil;

var MIXIN_MATCH = styleUtil.rx.MIXIN_MATCH;
var VAR_ASSIGN = styleUtil.rx.VAR_ASSIGN;
var VAR_MATCH = styleUtil.rx.VAR_MATCH;
var APPLY_NAME_CLEAN = /;\s*/m;

// separator used between mixin-name and mixin-property-name when producing properties
// NOTE: plain '-' may cause collisions in user styles
var MIXIN_VAR_SEP = '_-_';

// map of mixin to property names
// --foo: {border: 2px} -> (--foo, ['border'])
var mixinMap = {};

function mapSet(name, prop) {
name = name.trim();
mixinMap[name] = prop;
}

function mapGet(name) {
name = name.trim();
return mixinMap[name];
}

// "parse" a mixin definition into a map of properties and values
// cssTextToMap('border: 2px solid black') -> ('border', '2px solid black')
function cssTextToMap(text) {
var props = text.split(';');
var out = {};
for (var i = 0, p, sp; i < props.length; i++) {
p = props[i];
if (p) {
sp = p.split(':');
// ignore lines that aren't definitions like @media
if (sp.length > 1) {
// some properties may have ':' in the value, like data urls
out[sp[0].trim()] = sp.slice(1).join(':');
}
}
}
return out;
}

function produceCssProperties(matchText, propertyName, valueProperty, valueMixin) {
// handle case where property value is a mixin
if (valueProperty) {
VAR_MATCH.lastIndex = 0;
var m = VAR_MATCH.exec(valueProperty);
if (m) {
var value = m[2];
if (mapGet(value)){
valueMixin = '@apply ' + value + ';';
}
}
}
if (!valueMixin) {
return matchText;
}
var mixinAsProperties = consumeCssProperties(valueMixin);
var prefix = matchText.slice(0, matchText.indexOf('--'));
var mixinValues = cssTextToMap(mixinAsProperties);
var oldProperties = mapGet(propertyName);
var combinedProps = mixinValues;
if (oldProperties) {
// NOTE: since we use mixin, the map of properties is updated here
// and this is what we want.
combinedProps = Polymer.Base.mixin(oldProperties, mixinValues);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the else here? Don't we always want to update the property map with the list of all properties that have been used for that mixin?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using mixin, the reference in the map is always the same and we don't need to set again.

mapSet(propertyName, combinedProps);
}
var out = [];
var p, v;
// set variables defined by current mixin
for (p in combinedProps) {
v = mixinValues[p];
// if property not defined by current mixin, set initial
if (v === undefined) {
v = 'initial';
}
out.push(propertyName + MIXIN_VAR_SEP + p + ': ' + v);
}
return prefix + out.join('; ') + ';';
}

// fix shim'd var syntax
// var(--a, --b) -> var(--a, var(--b));
function fixVars(matchText, prefix, value, fallback) {
// if fallback doesn't exist, or isn't a broken variable, abort
if (!fallback || fallback.indexOf('--') !== 0) {
return matchText;
}
return [prefix, 'var(', value, ', var(', fallback, '));'].join('');
}

// produce variable consumption at the site of mixin consumption
// @apply --foo; -> for all props (${propname}: var(--foo_-_${propname}, ${fallback[propname]}}))
// Example:
// border: var(--foo_-_border); padding: var(--foo_-_padding, 2px)
function atApplyToCssProperties(mixinName, fallbacks) {
mixinName = mixinName.replace(APPLY_NAME_CLEAN, '');
var vars = [];
var mixinProperties = mapGet(mixinName);
if (mixinProperties) {
var p, parts, f;
for (p in mixinProperties) {
f = fallbacks && fallbacks[p];
parts = [p, ': var(', mixinName, MIXIN_VAR_SEP, p];
if (f) {
parts.push(',', f);
}
parts.push(')');
vars.push(parts.join(''));
}
}
return vars.join('; ');
}

// replace mixin consumption with variable consumption
function consumeCssProperties(text) {
var m;
// loop over text until all mixins with defintions have been applied
while((m = MIXIN_MATCH.exec(text))) {
var matchText = m[0];
var mixinName = m[1];
var idx = m.index;
// collect properties before apply to be "defaults" if mixin might override them
// match includes a "prefix", so find the start and end positions of @apply
var applyPos = idx + matchText.indexOf('@apply');
var afterApplyPos = idx + matchText.length;
// find props defined before this @apply
var textBeforeApply = text.slice(0, applyPos);
var textAfterApply = text.slice(afterApplyPos);
var defaults = cssTextToMap(textBeforeApply);
var replacement = atApplyToCssProperties(mixinName, defaults);
// use regex match position to replace mixin, keep linear processing time
text = [textBeforeApply, replacement, textAfterApply].join('');
// move regex search to _after_ replacement
MIXIN_MATCH.lastIndex = idx + replacement.length;
}
return text;
}

var ApplyShim = {
_map: mixinMap,
_separator: MIXIN_VAR_SEP,
transform: function(styles) {
styleUtil.forRulesInStyles(styles, this._boundTransformRule);
},
transformRule: function(rule) {
rule.cssText = this.transformCssText(rule.parsedCssText);
// :root was only used for variable assignment in property shim,
// but generates invalid selectors with real properties.
// replace with `:host > *`, which serves the same effect
if (rule.selector === ':root') {
rule.selector = ':host > *';
}
},
transformCssText: function(cssText) {
// fix shim variables
cssText = cssText.replace(VAR_MATCH, fixVars);
// produce variables
cssText = cssText.replace(VAR_ASSIGN, produceCssProperties);
// consume mixins
return consumeCssProperties(cssText);
}
};

ApplyShim._boundTransformRule = ApplyShim.transformRule.bind(ApplyShim);
return ApplyShim;
})();
</script>
4 changes: 2 additions & 2 deletions src/lib/css-parse.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
var cssText = '';
if (node.cssText || node.rules) {
var r$ = node.rules;
if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
if (r$ && !this._hasMixinRules(r$)) {
for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
cssText = this.stringify(r, preserveProperties, cssText);
}
Expand Down Expand Up @@ -175,7 +175,7 @@
port: /@import[^;]*;/gim,
customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
keyframesRule: /^@[^\s]*keyframes/,
multipleSpaces: /\s+/g
Expand Down
Loading