OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="style-util.html"> |
| 12 |
| 13 <script> |
| 14 |
| 15 (function() { |
| 16 |
| 17 /* Transforms ShadowDOM styling into ShadyDOM styling |
| 18 |
| 19 * scoping: |
| 20 |
| 21 * elements in scope get scoping selector class="x-foo-scope" |
| 22 * selectors re-written as follows: |
| 23 |
| 24 div button -> div.x-foo-scope button.x-foo-scope |
| 25 |
| 26 * :host -> scopeName |
| 27 |
| 28 * :host(...) -> scopeName... |
| 29 |
| 30 * ::content -> ' ' NOTE: requires use of scoping selector and selectors |
| 31 cannot otherwise be scoped: |
| 32 e.g. :host ::content > .bar -> x-foo > .bar |
| 33 |
| 34 * ::shadow, /deep/: processed simimlar to ::content |
| 35 |
| 36 * :host-context(...): NOT SUPPORTED |
| 37 |
| 38 */ |
| 39 |
| 40 // Given a node and scope name, add a scoping class to each node |
| 41 // in the tree. This facilitates transforming css into scoped rules. |
| 42 function transformDom(node, scope, useAttr, shouldRemoveScope) { |
| 43 _transformDom(node, scope || '', useAttr, shouldRemoveScope); |
| 44 } |
| 45 |
| 46 function _transformDom(node, selector, useAttr, shouldRemoveScope) { |
| 47 if (node.setAttribute) { |
| 48 transformElement(node, selector, useAttr, shouldRemoveScope); |
| 49 } |
| 50 var c$ = Polymer.dom(node).childNodes; |
| 51 for (var i=0; i<c$.length; i++) { |
| 52 _transformDom(c$[i], selector, useAttr, shouldRemoveScope); |
| 53 } |
| 54 } |
| 55 |
| 56 function transformElement(element, scope, useAttr, shouldRemoveScope) { |
| 57 if (useAttr) { |
| 58 if (shouldRemoveScope) { |
| 59 element.removeAttribute(SCOPE_NAME); |
| 60 } else { |
| 61 element.setAttribute(SCOPE_NAME, scope); |
| 62 } |
| 63 } else { |
| 64 // note: if using classes, we add both the general 'style-scope' class |
| 65 // as well as the specific scope. This enables easy filtering of all |
| 66 // `style-scope` elements |
| 67 if (scope) { |
| 68 if (shouldRemoveScope) { |
| 69 element.classList.remove(SCOPE_NAME, scope); |
| 70 } else { |
| 71 element.classList.add(SCOPE_NAME, scope); |
| 72 } |
| 73 } |
| 74 } |
| 75 } |
| 76 |
| 77 function transformHost(host, scope) { |
| 78 } |
| 79 |
| 80 // Given a string of cssText and a scoping string (scope), returns |
| 81 // a string of scoped css where each selector is transformed to include |
| 82 // a class created from the scope. ShadowDOM selectors are also transformed |
| 83 // (e.g. :host) to use the scoping selector. |
| 84 function transformCss(rules, scope, ext, callback, useAttr) { |
| 85 var hostScope = calcHostScope(scope, ext); |
| 86 scope = calcElementScope(scope, useAttr); |
| 87 return Polymer.StyleUtil.toCssText(rules, function(rule) { |
| 88 transformRule(rule, scope, hostScope); |
| 89 if (callback) { |
| 90 callback(rule, scope, hostScope); |
| 91 } |
| 92 }); |
| 93 } |
| 94 |
| 95 function calcElementScope(scope, useAttr) { |
| 96 if (scope) { |
| 97 return useAttr ? |
| 98 CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : |
| 99 CSS_CLASS_PREFIX + scope; |
| 100 } else { |
| 101 return ''; |
| 102 } |
| 103 } |
| 104 |
| 105 function calcHostScope(scope, ext) { |
| 106 return ext ? '[is=' + scope + ']' : scope; |
| 107 } |
| 108 |
| 109 function transformRule(rule, scope, hostScope) { |
| 110 _transformRule(rule, transformComplexSelector, |
| 111 scope, hostScope); |
| 112 } |
| 113 |
| 114 // transforms a css rule to a scoped rule. |
| 115 function _transformRule(rule, transformer, scope, hostScope) { |
| 116 var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); |
| 117 for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { |
| 118 p$[i] = transformer(p, scope, hostScope); |
| 119 } |
| 120 rule.selector = p$.join(COMPLEX_SELECTOR_SEP); |
| 121 } |
| 122 |
| 123 function transformComplexSelector(selector, scope, hostScope) { |
| 124 var stop = false; |
| 125 selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) { |
| 126 if (!stop) { |
| 127 var o = transformCompoundSelector(s, c, scope, hostScope); |
| 128 if (o.stop) { |
| 129 stop = true; |
| 130 } |
| 131 c = o.combinator; |
| 132 s = o.value; |
| 133 } |
| 134 return c + s; |
| 135 }); |
| 136 return selector; |
| 137 } |
| 138 |
| 139 function transformCompoundSelector(selector, combinator, scope, hostScope) { |
| 140 // replace :host with host scoping class |
| 141 var jumpIndex = selector.search(SCOPE_JUMP); |
| 142 if (selector.indexOf(HOST) >=0) { |
| 143 // :host(...) |
| 144 selector = selector.replace(HOST_PAREN, function(m, host, paren) { |
| 145 return hostScope + paren; |
| 146 }); |
| 147 // now normal :host |
| 148 selector = selector.replace(HOST, hostScope); |
| 149 // replace other selectors with scoping class |
| 150 } else if (jumpIndex !== 0) { |
| 151 selector = scope ? transformSimpleSelector(selector, scope) : selector; |
| 152 } |
| 153 // remove left-side combinator when dealing with ::content. |
| 154 if (selector.indexOf(CONTENT) >= 0) { |
| 155 combinator = ''; |
| 156 } |
| 157 // process scope jumping selectors up to the scope jump and then stop |
| 158 // e.g. .zonk ::content > .foo ==> .zonk.scope > .foo |
| 159 var stop; |
| 160 if (jumpIndex >= 0) { |
| 161 selector = selector.replace(SCOPE_JUMP, ' '); |
| 162 stop = true; |
| 163 } |
| 164 return {value: selector, combinator: combinator, stop: stop}; |
| 165 } |
| 166 |
| 167 function transformSimpleSelector(selector, scope) { |
| 168 var p$ = selector.split(PSEUDO_PREFIX); |
| 169 p$[0] += scope; |
| 170 return p$.join(PSEUDO_PREFIX); |
| 171 } |
| 172 |
| 173 function transformRootRule(rule) { |
| 174 _transformRule(rule, transformRootSelector); |
| 175 } |
| 176 |
| 177 function transformRootSelector(selector) { |
| 178 return selector.match(SCOPE_JUMP) ? |
| 179 transformComplexSelector(selector) : |
| 180 selector.trim() + SCOPE_ROOT_SELECTOR; |
| 181 } |
| 182 |
| 183 var SCOPE_NAME = 'style-scope'; |
| 184 var SCOPE_ROOT_SELECTOR = ':not([' + SCOPE_NAME + '])' + |
| 185 ':not(.' + SCOPE_NAME + ')'; |
| 186 var COMPLEX_SELECTOR_SEP = ','; |
| 187 var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g; |
| 188 var HOST = ':host'; |
| 189 // NOTE: this supports 1 nested () pair for things like |
| 190 // :host(:not([selected]), more general support requires |
| 191 // parsing which seems like overkill |
| 192 var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g; |
| 193 var CONTENT = '::content'; |
| 194 var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//; |
| 195 var CSS_CLASS_PREFIX = '.'; |
| 196 var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~='; |
| 197 var CSS_ATTR_SUFFIX = ']'; |
| 198 var PSEUDO_PREFIX = ':'; |
| 199 |
| 200 // exports |
| 201 Polymer.StyleTransformer = { |
| 202 element: transformElement, |
| 203 dom: transformDom, |
| 204 host: transformHost, |
| 205 css: transformCss, |
| 206 rule: transformRule, |
| 207 rootRule: transformRootRule, |
| 208 SCOPE_NAME: SCOPE_NAME |
| 209 }; |
| 210 |
| 211 })(); |
| 212 |
| 213 </script> |
OLD | NEW |