Home | History | Annotate | Download | only in core-style
      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 &#64;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