OLD | NEW |
(Empty) | |
| 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.g
ithub.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/CONTRI
BUTORS.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/PATEN
TS.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> |
OLD | NEW |