1 <!-- 2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 Code distributed by Google as part of the polymer project is also 7 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 --> 9 <!-- 10 11 The `core-style` element helps manage styling inside other elements and can 12 be used to make themes. The `core-style` element can be either a producer 13 or consumer of styling. If it has its `id` property set, it's a producer. 14 Elements that are producers should include css styling as their text content. 15 If a `core-style` has its `ref` property set, it's a consumer. A `core-style` 16 typically sets its `ref` property to the value of the `id` property of the 17 `core-style` it wants to use. This allows a single producer to be used in 18 multiple places, for example, in many different elements. 19 20 It's common to place `core-style` producer elements inside HTMLImports. 21 Remote stylesheets should be included this way, the @import css mechanism is 22 not currently supported. 23 24 Here's a basic example: 25 26 <polymer-element name="x-test" noscript> 27 <template> 28 <core-style ref="x-test"></core-style> 29 <content></content> 30 </template> 31 </polymer-element> 32 33 The `x-test` element above will be styled by any `core-style` elements that have 34 `id` set to `x-test`. These `core-style` producers are separate from the element 35 definition, allowing a user of the element to style it independent of the author's 36 styling. For example: 37 38 <core-style id="x-test"> 39 :host { 40 backgound-color: steelblue; 41 } 42 </core-style> 43 44 The content of the `x-test` `core-style` producer gets included inside the 45 shadowRoot of the `x-test` element. If the content of the `x-test` producer 46 `core-style` changes, all consumers of it are automatically kept in sync. This 47 allows updating styling on the fly. 48 49 The `core-style` element also supports bindings and it is the producer 50 `core-style` element is the model for its content. Here's an example: 51 52 <core-style id="x-test"> 53 :host { 54 background-color: {{myColor}}; 55 } 56 </core-style> 57 <script> 58 document._currentScript.ownerDocument.getElementById('x-test').myColor = 'orange'; 59 </script> 60 61 Finally, to facilitate sharing data between `core-style` elements, all 62 `core-style` elements have a `g` property which is set to the global 63 `CoreStyle.g`. Here's an example: 64 65 <core-style id="x-test"> 66 :host { 67 background-color: {{g.myColor}}; 68 } 69 </core-style> 70 <script> 71 CoreStyle.g.myColor = 'tomato'; 72 </script> 73 74 Finally, one `core-style` can be nested inside another. The `core-style` 75 element has a `list` property which is a map of all the `core-style` producers. 76 A `core-style` producer's content is available via its `cssText` property. 77 Putting this together: 78 79 <core-style id="common"> 80 :host { 81 font-family: sans-serif; 82 } 83 </core-style> 84 85 <core-style id="x-test"> 86 {{list.common.cssText}} 87 88 :host { 89 background-color: {{g.myColor}}; 90 } 91 </core-style> 92 93 94 @group Polymer Core Elements 95 @element core-style 96 @homepage github.io 97 --> 98 99 <link rel="import" href="../polymer/polymer.html"> 100 101 <polymer-element name="core-style" hidden> 102 <script> 103 (function() { 104 105 window.CoreStyle = window.CoreStyle || { 106 g: {}, 107 list: {}, 108 refMap: {} 109 }; 110 111 Polymer('core-style', { 112 /** 113 * The `id` property should be set if the `core-style` is a producer 114 * of styles. In this case, the `core-style` should have text content 115 * that is cssText. 116 * 117 * @attribute id 118 * @type string 119 * @default '' 120 */ 121 122 123 publish: { 124 /** 125 * The `ref` property should be set if the `core-style` element is a 126 * consumer of styles. Set it to the `id` of the desired `core-style` 127 * element. 128 * 129 * @attribute ref 130 * @type string 131 * @default '' 132 */ 133 ref: '' 134 }, 135 136 // static 137 g: CoreStyle.g, 138 refMap: CoreStyle.refMap, 139 140 /** 141 * The `list` is a map of all `core-style` producers stored by `id`. It 142 * should be considered readonly. It's useful for nesting one `core-style` 143 * inside another. 144 * 145 * @attribute list 146 * @type object (readonly) 147 * @default {map of all `core-style` producers} 148 */ 149 list: CoreStyle.list, 150 151 // if we have an id, we provide style 152 // if we have a ref, we consume/require style 153 ready: function() { 154 if (this.id) { 155 this.provide(); 156 } else { 157 this.registerRef(this.ref); 158 if (!window.ShadowDOMPolyfill) { 159 this.require(); 160 } 161 } 162 }, 163 164 // can't shim until attached if using SD polyfill because need to find host 165 attached: function() { 166 if (!this.id && window.ShadowDOMPolyfill) { 167 this.require(); 168 } 169 }, 170 171 /****** producer stuff *******/ 172 173 provide: function() { 174 this.register(); 175 // we want to do this asap, especially so we can do so before definitions 176 // that use this core-style are registered. 177 if (this.textContent) { 178 this._completeProvide(); 179 } else { 180 this.async(this._completeProvide); 181 } 182 }, 183 184 register: function() { 185 var i = this.list[this.id]; 186 if (i) { 187 if (!Array.isArray(i)) { 188 this.list[this.id] = [i]; 189 } 190 this.list[this.id].push(this); 191 } else { 192 this.list[this.id] = this; 193 } 194 }, 195 196 // stamp into a shadowRoot so we can monitor dom of the bound output 197 _completeProvide: function() { 198 this.createShadowRoot(); 199 this.domObserver = new MutationObserver(this.domModified.bind(this)) 200 .observe(this.shadowRoot, {subtree: true, 201 characterData: true, childList: true}); 202 this.provideContent(); 203 }, 204 205 provideContent: function() { 206 this.ensureTemplate(); 207 this.shadowRoot.textContent = ''; 208 this.shadowRoot.appendChild(this.instanceTemplate(this.template)); 209 this.cssText = this.shadowRoot.textContent; 210 }, 211 212 ensureTemplate: function() { 213 if (!this.template) { 214 this.template = this.querySelector('template:not([repeat]):not([bind])'); 215 // move content into the template 216 if (!this.template) { 217 this.template = document.createElement('template'); 218 var n = this.firstChild; 219 while (n) { 220 this.template.content.appendChild(n.cloneNode(true)); 221 n = n.nextSibling; 222 } 223 } 224 } 225 }, 226 227 domModified: function() { 228 this.cssText = this.shadowRoot.textContent; 229 this.notify(); 230 }, 231 232 // notify instances that reference this element 233 notify: function() { 234 var s$ = this.refMap[this.id]; 235 if (s$) { 236 for (var i=0, s; (s=s$[i]); i++) { 237 s.require(); 238 } 239 } 240 }, 241 242 /****** consumer stuff *******/ 243 244 registerRef: function(ref) { 245 //console.log('register', ref); 246 this.refMap[this.ref] = this.refMap[this.ref] || []; 247 this.refMap[this.ref].push(this); 248 }, 249 250 applyRef: function(ref) { 251 this.ref = ref; 252 this.registerRef(this.ref); 253 this.require(); 254 }, 255 256 require: function() { 257 var cssText = this.cssTextForRef(this.ref); 258 //console.log('require', this.ref, cssText); 259 if (cssText) { 260 this.ensureStyleElement(); 261 // do nothing if cssText has not changed 262 if (this.styleElement._cssText === cssText) { 263 return; 264 } 265 this.styleElement._cssText = cssText; 266 if (window.ShadowDOMPolyfill) { 267 this.styleElement.textContent = cssText; 268 cssText = Platform.ShadowCSS.shimStyle(this.styleElement, 269 this.getScopeSelector()); 270 } 271 this.styleElement.textContent = cssText; 272 } 273 }, 274 275 cssTextForRef: function(ref) { 276 var s$ = this.byId(ref); 277 var cssText = ''; 278 if (s$) { 279 if (Array.isArray(s$)) { 280 var p = []; 281 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { 282 p.push(s.cssText); 283 } 284 cssText = p.join('\n\n'); 285 } else { 286 cssText = s$.cssText; 287 } 288 } 289 if (s$ && !cssText) { 290 console.warn('No styles provided for ref:', ref); 291 } 292 return cssText; 293 }, 294 295 byId: function(id) { 296 return this.list[id]; 297 }, 298 299 ensureStyleElement: function() { 300 if (!this.styleElement) { 301 this.styleElement = window.ShadowDOMPolyfill ? 302 this.makeShimStyle() : 303 this.makeRootStyle(); 304 } 305 if (!this.styleElement) { 306 console.warn(this.localName, 'could not setup style.'); 307 } 308 }, 309 310 makeRootStyle: function() { 311 var style = document.createElement('style'); 312 this.appendChild(style); 313 return style; 314 }, 315 316 makeShimStyle: function() { 317 var host = this.findHost(this); 318 if (host) { 319 var name = host.localName; 320 var style = document.querySelector('style[' + name + '=' + this.ref +']'); 321 if (!style) { 322 style = document.createElement('style'); 323 style.setAttribute(name, this.ref); 324 document.head.appendChild(style); 325 } 326 return style; 327 } 328 }, 329 330 getScopeSelector: function() { 331 if (!this._scopeSelector) { 332 var selector = '', host = this.findHost(this); 333 if (host) { 334 var typeExtension = host.hasAttribute('is'); 335 var name = typeExtension ? host.getAttribute('is') : host.localName; 336 selector = Platform.ShadowCSS.makeScopeSelector(name, 337 typeExtension); 338 } 339 this._scopeSelector = selector; 340 } 341 return this._scopeSelector; 342 }, 343 344 findHost: function(node) { 345 while (node.parentNode) { 346 node = node.parentNode; 347 } 348 return node.host || wrap(document.documentElement); 349 }, 350 351 /* filters! */ 352 // TODO(dfreedm): add more filters! 353 354 cycle: function(rgb, amount) { 355 if (rgb.match('#')) { 356 var o = this.hexToRgb(rgb); 357 if (!o) { 358 return rgb; 359 } 360 rgb = 'rgb(' + o.r + ',' + o.b + ',' + o.g + ')'; 361 } 362 363 function cycleChannel(v) { 364 return Math.abs((Number(v) - amount) % 255); 365 } 366 367 return rgb.replace(/rgb\(([^,]*),([^,]*),([^,]*)\)/, function(m, a, b, c) { 368 return 'rgb(' + cycleChannel(a) + ',' + cycleChannel(b) + ', ' 369 + cycleChannel(c) + ')'; 370 }); 371 }, 372 373 hexToRgb: function(hex) { 374 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 375 return result ? { 376 r: parseInt(result[1], 16), 377 g: parseInt(result[2], 16), 378 b: parseInt(result[3], 16) 379 } : null; 380 } 381 382 }); 383 384 385 })(); 386 </script> 387 </polymer-element> 388