Skip to content

Commit

Permalink
Merge branch 'shimStyling'
Browse files Browse the repository at this point in the history
  • Loading branch information
sorvell committed Jun 7, 2013
2 parents c7f6a8d + d6b5b13 commit 63a1e0d
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 128 deletions.
188 changes: 138 additions & 50 deletions src/shimStyling.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,59 @@
background: red;
}
* functional encapsultion: Styles defined within shadowDOM, apply only to
dom inside the shadowDOM. To shim this feature, non-@host rules within
style elements are prefixed with a given scope name. Thus, they apply via
a descendent selector to the dom inside the shadowRoot.
For example, given a scope name of .foo, a rule like this:
* encapsultion: Styles defined within shadowDOM, apply only to
dom inside the shadowDOM. Polymer uses one of two techniques to imlement
this feature.
By default, rules are prefixed with the host element tag name
as a descendant selector. This ensures styling does not leak out of the 'top'
of the element's shadowDOM. For example,
div {
font-weight: bold;
}
becomes:
x-foo div {
font-weight: bold;
}
becomes:
Alternatively, if Polymer.strictPolyfillStyling is set to true then
selectors are scoped by adding an attribute selector suffix to each
simple selector that contains the host element tag name. Each element
in the element's shadowDOM template is also given the scope attribute.
Thus, these rules match only elements that have the scope attribute.
For example, given a scope name of x-foo, a rule like this:
div {
font-weight: bold;
}
becomes:
.foo div {
div[x-foo] {
font-weight: bold;
}
Note that elements that are dynamically added to a scope must have the scope
selector added to them manually.
* ::pseudo: These rules are converted to rules that take advantage of the
pseudo attribute. For example, a shadowRoot like this inside an x-foo
<div pseudo="x-special">Special</div>
with a rule like this:
x-foo::x-special { ... }
becomes:
x-foo [pseudo=x-special] { ... }
Unaddressed shadowDOM styling features:
Expand Down Expand Up @@ -94,18 +132,6 @@
Note the use of @polyfill in the comment above a shadowDOM specific style
declaration. This is a directive to the styling shim to use the selector
in comments in lieu of the next selector when running under polyfill.
* ::pseudo: This behavior is not emulated. Users can create an extra
rule to target the pseudo node directly. Given a shadowRoot like this:
<div pseudo="x-special">Special</div>
This can be styled using native and polyfilled shadowDOM as follows:
/ *@polyfill x-foo [pseudo=x-special] * /
x-foo::x-special {
color: orange;
}
*/
(function(scope) {

Expand All @@ -116,9 +142,11 @@ var slice = Array.prototype.slice.call.bind(Array.prototype.slice);
var stylizer = {
hostRuleRe: /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
selectorRe: /([^{]*)({[\s\S]*?})/gim,
hostElementRe: /(.*)((?:\*)|(?:\:scope))(.*)/,
hostFixableRe: /^[.\[:]/,
cssCommentRe: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
cssPolyfillCommentRe: /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
cssPseudoRe: /::(x-[^\s{,(]*)/gim,
selectorReSuffix: '([>\\s~+\[.,{:][\\s\\S]*)?$',
hostRe: /@host/gim,
cache: {},
Expand All @@ -130,6 +158,9 @@ var stylizer = {
stylizer.cacheDefinition(element);
stylizer.shimPolyfillDirectives(element.styles, name);
// find styles and apply shimming...
if (Polymer.strictPolyfillStyling) {
stylizer.applyScopeToContent(element.templateContent, name);
}
stylizer.applyShimming(stylizer.stylesForElement(element), name);
}
},
Expand All @@ -144,10 +175,10 @@ var stylizer = {
}
},
applyShimming: function(styles, name) {
this.shimAtHost(styles, name);
this.shimScoping(styles, name);
var cssText = this.shimAtHost(styles, name);
cssText += this.shimScoping(styles, name);
this.addCssToDocument(cssText);
},
//TODO(sorvell): use SideTable
cacheDefinition: function(element) {
var name = element.options.name;
var template = element.querySelector('template');
Expand All @@ -157,6 +188,16 @@ var stylizer = {
element.templateContent = content;
stylizer.cache[name] = element;
},
applyScopeToContent: function(root, name) {
if (root) {
forEach(root.querySelectorAll('*'), function(node) {
node.setAttribute(name, '');
});
forEach(root.querySelectorAll('template'), function(template) {
this.applyScopeToContent(templateContent(template), name);
}, this);
}
},
stylesForElement: function(element) {
var styles = element.styles;
var shadow = element.templateContent &&
Expand Down Expand Up @@ -201,8 +242,7 @@ var stylizer = {
// becomes: scopeName.foo { declarations }
shimAtHost: function(styles, name) {
if (styles) {
var cssText = this.convertAtHostStyles(styles, name);
this.addCssToDocument(cssText);
return this.convertAtHostStyles(styles, name);
}
},
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
Expand All @@ -215,15 +255,16 @@ var stylizer = {
*/
shimScoping: function(styles, name) {
if (styles) {
this.applyPseudoScoping(styles, name);
return this.convertScopedStyles(styles, name);
}
},
convertPolyfillDirectives: function(cssText, name) {
var r = '', l = 0, matches;
var r = '', l = 0, matches, selector;
while (matches=this.cssPolyfillCommentRe.exec(cssText)) {
r += cssText.substring(l, matches.index);
// remove end comment delimiter (*/)
r += matches[1].slice(0, -2) + '{';
selector = matches[1].slice(0, -2).replace('@host', name);
r += this.scopeSelector(selector, name) + '{';
l = this.cssPolyfillCommentRe.lastIndex;
}
r += cssText.substring(l, cssText.length);
Expand Down Expand Up @@ -262,57 +303,103 @@ var stylizer = {
}
return r;
},
// supports scopig by name and [is=name] syntax
scopeHostSelector: function(selector, name) {
var r = [], parts = selector.split(',');
var r = [], parts = selector.split(','), is = '[is=' + name + ']';
parts.forEach(function(p) {
p = p.trim();
// selector: * -> name
if (p.indexOf('*') >= 0) {
p = p.replace('*', name);
// selector: *|:scope -> name
if (p.match(this.hostElementRe)) {
p = p.replace(this.hostElementRe, name + '$1$3, ' + is + '$1$3');
// selector: .foo -> name.foo, [bar] -> name[bar]
} else if (p.match(this.hostFixableRe)) {
p = name + p;
p = name + p + ', ' + is + p;
}
r.push(p);
}, this);
return r.join(', ');
},
applyPseudoScoping: function(styles, name) {
convertScopedStyles: function(styles, name) {
forEach(styles, function(s) {
if (s.parentNode) {
s.parentNode.removeChild(s);
}
});
// TODO(sorvell): remove @host rules (use cssom rather than regex?)
var cssText = this.stylesToCssText(styles).replace(this.hostRuleRe, '');
var rules = this.cssToRules(cssText);
var cssText = this.pseudoScopeRules(rules, name);
this.addCssToDocument(cssText);
cssText = this.scopeRules(rules, name);
return this.convertPseudos(cssText);
},
convertPseudos: function(cssText) {
return cssText.replace(this.cssPseudoRe, ' [pseudo=$1]');
},
// change a selector like 'div' to 'name div'
pseudoScopeRules: function(cssRules, name) {
scopeRules: function(cssRules, name) {
var cssText = '';
forEach(cssRules, function(rule) {
if (rule.selectorText && (rule.style && rule.style.cssText)) {
cssText += this.pseudoScopeSelector(rule.selectorText, name) + ' {\n\t';
cssText += rule.style.cssText + '\n}\n\n';
cssText += this.scopeSelector(rule.selectorText, name,
Polymer.strictPolyfillStyling) + ' {\n\t';
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
} else if (rule.media) {
cssText += '@media ' + rule.media.mediaText + ' {\n';
cssText += this.pseudoScopeRules(rule.cssRules, name);
cssText += this.scopeRules(rule.cssRules, name);
cssText += '\n}\n\n';
} else if (rule.cssText) {
cssText += rule.cssText + '\n\n';
}
}, this);
return cssText;
},
pseudoScopeSelector: function(selector, name) {
propertiesFromRule: function(rule) {
var properties = rule.style.cssText;
// TODO(sorvell): Chrome cssom incorrectly removes quotes from the content
// property. (https://code.google.com/p/chromium/issues/detail?id=247231)
if (rule.style.content && !rule.style.content.match(/['"]+/)) {
properties = 'content: \'' + rule.style.content + '\';\n' +
rule.style.cssText.replace(/content:[^;]*;/g, '');
}
return properties;
},
selectorNeedsScoping: function(selector, name) {
var matchScope = '(' + name + '|\\[is=' + name + '\\])';
var selectorRe = new RegExp('^' + matchScope + this.selectorReSuffix, 'm');
return !selector.match(selectorRe);
},
scopeSelector: function(selector, name, strict) {
var r = [], parts = selector.split(',');
parts.forEach(function(p) {
r.push(name + ' ' + p.trim());
});
p = p.trim();
if (this.selectorNeedsScoping(p, name)) {
p = strict ? this.applyStrictSelectorScope(p, name) :
this.applySimpleSelectorScope(p, name);
}
r.push(p);
}, this);
return r.join(', ');
},
// scope via name and [is=name]
applySimpleSelectorScope: function(selector, name) {
return name + ' ' + selector + ', ' + '[is=' + name + '] ' + selector;
},
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
applyStrictSelectorScope: function(selector, name) {
var splits = [' ', '>', '+', '~'],
scoped = selector,
attrName = '[' + name + ']';
splits.forEach(function(sep) {
var parts = scoped.split(sep);
scoped = parts.map(function(p) {
var t = p.trim();
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
}
return p;
}).join(sep);
});
return scoped;
},
stylesToCssText: function(styles, preserveComments) {
var cssText = '';
forEach(styles, function(s) {
Expand Down Expand Up @@ -350,25 +437,26 @@ var stylizer = {
getSheet: function() {
if (!this.sheet) {
this.sheet = document.createElement("style");
this.sheet.setAttribute('polymer-polyfill', '');
}
return this.sheet;
},
apply: function() {
addSheetToDocument: function() {
this.addCssToDocument('style { display: none !important; }\n');
// TODO(sorvell): change back to insertBefore when ShadowDOM polyfill
// supports this.
document.head.appendChild(this.getSheet());
//document.head.insertBefore(this.getSheet(), doc.head.children[0]);
var head = document.querySelector('head');
head.insertBefore(this.getSheet(), head.childNodes[0]);
}
};

document.addEventListener('WebComponentsReady', function() {
stylizer.apply();
})
// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
stylizer.addSheetToDocument();
}

// exports
Polymer.shimStyling = stylizer.shimStyling;
Polymer.shimShadowDOMStyling = stylizer.shimShadowDOMStyling;
Polymer.shimPolyfillDirectives = stylizer.shimPolyfillDirectives.bind(stylizer);
Polymer.strictPolyfillStyling = false;

})(window);
Loading

0 comments on commit 63a1e0d

Please sign in to comment.