1 2(function() { 3 4window.CoreStyle = window.CoreStyle || { 5 g: {}, 6 list: {}, 7 refMap: {} 8}; 9 10Polymer('core-style', { 11 /** 12 * The `id` property should be set if the `core-style` is a producer 13 * of styles. In this case, the `core-style` should have text content 14 * that is cssText. 15 * 16 * @attribute id 17 * @type string 18 * @default '' 19 */ 20 21 22 publish: { 23 /** 24 * The `ref` property should be set if the `core-style` element is a 25 * consumer of styles. Set it to the `id` of the desired `core-style` 26 * element. 27 * 28 * @attribute ref 29 * @type string 30 * @default '' 31 */ 32 ref: '' 33 }, 34 35 // static 36 g: CoreStyle.g, 37 refMap: CoreStyle.refMap, 38 39 /** 40 * The `list` is a map of all `core-style` producers stored by `id`. It 41 * should be considered readonly. It's useful for nesting one `core-style` 42 * inside another. 43 * 44 * @attribute list 45 * @type object (readonly) 46 * @default {map of all `core-style` producers} 47 */ 48 list: CoreStyle.list, 49 50 // if we have an id, we provide style 51 // if we have a ref, we consume/require style 52 ready: function() { 53 if (this.id) { 54 this.provide(); 55 } else { 56 this.registerRef(this.ref); 57 if (!window.ShadowDOMPolyfill) { 58 this.require(); 59 } 60 } 61 }, 62 63 // can't shim until attached if using SD polyfill because need to find host 64 attached: function() { 65 if (!this.id && window.ShadowDOMPolyfill) { 66 this.require(); 67 } 68 }, 69 70 /****** producer stuff *******/ 71 72 provide: function() { 73 this.register(); 74 // we want to do this asap, especially so we can do so before definitions 75 // that use this core-style are registered. 76 if (this.textContent) { 77 this._completeProvide(); 78 } else { 79 this.async(this._completeProvide); 80 } 81 }, 82 83 register: function() { 84 var i = this.list[this.id]; 85 if (i) { 86 if (!Array.isArray(i)) { 87 this.list[this.id] = [i]; 88 } 89 this.list[this.id].push(this); 90 } else { 91 this.list[this.id] = this; 92 } 93 }, 94 95 // stamp into a shadowRoot so we can monitor dom of the bound output 96 _completeProvide: function() { 97 this.createShadowRoot(); 98 this.domObserver = new MutationObserver(this.domModified.bind(this)) 99 .observe(this.shadowRoot, {subtree: true, 100 characterData: true, childList: true}); 101 this.provideContent(); 102 }, 103 104 provideContent: function() { 105 this.ensureTemplate(); 106 this.shadowRoot.textContent = ''; 107 this.shadowRoot.appendChild(this.instanceTemplate(this.template)); 108 this.cssText = this.shadowRoot.textContent; 109 }, 110 111 ensureTemplate: function() { 112 if (!this.template) { 113 this.template = this.querySelector('template:not([repeat]):not([bind])'); 114 // move content into the template 115 if (!this.template) { 116 this.template = document.createElement('template'); 117 var n = this.firstChild; 118 while (n) { 119 this.template.content.appendChild(n.cloneNode(true)); 120 n = n.nextSibling; 121 } 122 } 123 } 124 }, 125 126 domModified: function() { 127 this.cssText = this.shadowRoot.textContent; 128 this.notify(); 129 }, 130 131 // notify instances that reference this element 132 notify: function() { 133 var s$ = this.refMap[this.id]; 134 if (s$) { 135 for (var i=0, s; (s=s$[i]); i++) { 136 s.require(); 137 } 138 } 139 }, 140 141 /****** consumer stuff *******/ 142 143 registerRef: function(ref) { 144 //console.log('register', ref); 145 this.refMap[this.ref] = this.refMap[this.ref] || []; 146 this.refMap[this.ref].push(this); 147 }, 148 149 applyRef: function(ref) { 150 this.ref = ref; 151 this.registerRef(this.ref); 152 this.require(); 153 }, 154 155 require: function() { 156 var cssText = this.cssTextForRef(this.ref); 157 //console.log('require', this.ref, cssText); 158 if (cssText) { 159 this.ensureStyleElement(); 160 // do nothing if cssText has not changed 161 if (this.styleElement._cssText === cssText) { 162 return; 163 } 164 this.styleElement._cssText = cssText; 165 if (window.ShadowDOMPolyfill) { 166 this.styleElement.textContent = cssText; 167 cssText = Platform.ShadowCSS.shimStyle(this.styleElement, 168 this.getScopeSelector()); 169 } 170 this.styleElement.textContent = cssText; 171 } 172 }, 173 174 cssTextForRef: function(ref) { 175 var s$ = this.byId(ref); 176 var cssText = ''; 177 if (s$) { 178 if (Array.isArray(s$)) { 179 var p = []; 180 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { 181 p.push(s.cssText); 182 } 183 cssText = p.join('\n\n'); 184 } else { 185 cssText = s$.cssText; 186 } 187 } 188 if (s$ && !cssText) { 189 console.warn('No styles provided for ref:', ref); 190 } 191 return cssText; 192 }, 193 194 byId: function(id) { 195 return this.list[id]; 196 }, 197 198 ensureStyleElement: function() { 199 if (!this.styleElement) { 200 this.styleElement = window.ShadowDOMPolyfill ? 201 this.makeShimStyle() : 202 this.makeRootStyle(); 203 } 204 if (!this.styleElement) { 205 console.warn(this.localName, 'could not setup style.'); 206 } 207 }, 208 209 makeRootStyle: function() { 210 var style = document.createElement('style'); 211 this.appendChild(style); 212 return style; 213 }, 214 215 makeShimStyle: function() { 216 var host = this.findHost(this); 217 if (host) { 218 var name = host.localName; 219 var style = document.querySelector('style[' + name + '=' + this.ref +']'); 220 if (!style) { 221 style = document.createElement('style'); 222 style.setAttribute(name, this.ref); 223 document.head.appendChild(style); 224 } 225 return style; 226 } 227 }, 228 229 getScopeSelector: function() { 230 if (!this._scopeSelector) { 231 var selector = '', host = this.findHost(this); 232 if (host) { 233 var typeExtension = host.hasAttribute('is'); 234 var name = typeExtension ? host.getAttribute('is') : host.localName; 235 selector = Platform.ShadowCSS.makeScopeSelector(name, 236 typeExtension); 237 } 238 this._scopeSelector = selector; 239 } 240 return this._scopeSelector; 241 }, 242 243 findHost: function(node) { 244 while (node.parentNode) { 245 node = node.parentNode; 246 } 247 return node.host || wrap(document.documentElement); 248 }, 249 250 /* filters! */ 251 // TODO(dfreedm): add more filters! 252 253 cycle: function(rgb, amount) { 254 if (rgb.match('#')) { 255 var o = this.hexToRgb(rgb); 256 if (!o) { 257 return rgb; 258 } 259 rgb = 'rgb(' + o.r + ',' + o.b + ',' + o.g + ')'; 260 } 261 262 function cycleChannel(v) { 263 return Math.abs((Number(v) - amount) % 255); 264 } 265 266 return rgb.replace(/rgb\(([^,]*),([^,]*),([^,]*)\)/, function(m, a, b, c) { 267 return 'rgb(' + cycleChannel(a) + ',' + cycleChannel(b) + ', ' 268 + cycleChannel(c) + ')'; 269 }); 270 }, 271 272 hexToRgb: function(hex) { 273 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 274 return result ? { 275 r: parseInt(result[1], 16), 276 g: parseInt(result[2], 16), 277 b: parseInt(result[3], 16) 278 } : null; 279 } 280 281}); 282 283 284})(); 285