1<!-- 2@license 3Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7Code distributed by Google as part of the polymer project is also 8subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9--> 10 11<link rel="import" href="../polymer/polymer.html"> 12<link rel="import" href="../iron-meta/iron-meta.html"> 13 14<script> 15 /** 16 * The `iron-iconset-svg` element allows users to define their own icon sets 17 * that contain svg icons. The svg icon elements should be children of the 18 * `iron-iconset-svg` element. Multiple icons should be given distinct id's. 19 * 20 * Using svg elements to create icons has a few advantages over traditional 21 * bitmap graphics like jpg or png. Icons that use svg are vector based so 22 * they are resolution independent and should look good on any device. They 23 * are stylable via css. Icons can be themed, colorized, and even animated. 24 * 25 * Example: 26 * 27 * <iron-iconset-svg name="my-svg-icons" size="24"> 28 * <svg> 29 * <defs> 30 * <g id="shape"> 31 * <rect x="12" y="0" width="12" height="24" /> 32 * <circle cx="12" cy="12" r="12" /> 33 * </g> 34 * </defs> 35 * </svg> 36 * </iron-iconset-svg> 37 * 38 * This will automatically register the icon set "my-svg-icons" to the iconset 39 * database. To use these icons from within another element, make a 40 * `iron-iconset` element and call the `byId` method 41 * to retrieve a given iconset. To apply a particular icon inside an 42 * element use the `applyIcon` method. For example: 43 * 44 * iconset.applyIcon(iconNode, 'car'); 45 * 46 * @element iron-iconset-svg 47 * @demo demo/index.html 48 * @implements {Polymer.Iconset} 49 */ 50 Polymer({ 51 is: 'iron-iconset-svg', 52 53 properties: { 54 55 /** 56 * The name of the iconset. 57 */ 58 name: { 59 type: String, 60 observer: '_nameChanged' 61 }, 62 63 /** 64 * The size of an individual icon. Note that icons must be square. 65 */ 66 size: { 67 type: Number, 68 value: 24 69 }, 70 71 /** 72 * Set to true to enable mirroring of icons where specified when they are 73 * stamped. Icons that should be mirrored should be decorated with a 74 * `mirror-in-rtl` attribute. 75 * 76 * NOTE: For performance reasons, direction will be resolved once per 77 * document per iconset, so moving icons in and out of RTL subtrees will 78 * not cause their mirrored state to change. 79 */ 80 rtlMirroring: { 81 type: Boolean, 82 value: false 83 } 84 }, 85 86 attached: function() { 87 this.style.display = 'none'; 88 }, 89 90 /** 91 * Construct an array of all icon names in this iconset. 92 * 93 * @return {!Array} Array of icon names. 94 */ 95 getIconNames: function() { 96 this._icons = this._createIconMap(); 97 return Object.keys(this._icons).map(function(n) { 98 return this.name + ':' + n; 99 }, this); 100 }, 101 102 /** 103 * Applies an icon to the given element. 104 * 105 * An svg icon is prepended to the element's shadowRoot if it exists, 106 * otherwise to the element itself. 107 * 108 * If RTL mirroring is enabled, and the icon is marked to be mirrored in 109 * RTL, the element will be tested (once and only once ever for each 110 * iconset) to determine the direction of the subtree the element is in. 111 * This direction will apply to all future icon applications, although only 112 * icons marked to be mirrored will be affected. 113 * 114 * @method applyIcon 115 * @param {Element} element Element to which the icon is applied. 116 * @param {string} iconName Name of the icon to apply. 117 * @return {?Element} The svg element which renders the icon. 118 */ 119 applyIcon: function(element, iconName) { 120 // insert svg element into shadow root, if it exists 121 element = element.root || element; 122 // Remove old svg element 123 this.removeIcon(element); 124 // install new svg element 125 var svg = this._cloneIcon(iconName, 126 this.rtlMirroring && this._targetIsRTL(element)); 127 if (svg) { 128 var pde = Polymer.dom(element); 129 pde.insertBefore(svg, pde.childNodes[0]); 130 return element._svgIcon = svg; 131 } 132 return null; 133 }, 134 135 /** 136 * Remove an icon from the given element by undoing the changes effected 137 * by `applyIcon`. 138 * 139 * @param {Element} element The element from which the icon is removed. 140 */ 141 removeIcon: function(element) { 142 // Remove old svg element 143 element = element.root || element; 144 if (element._svgIcon) { 145 Polymer.dom(element).removeChild(element._svgIcon); 146 element._svgIcon = null; 147 } 148 }, 149 150 /** 151 * Measures and memoizes the direction of the element. Note that this 152 * measurement is only done once and the result is memoized for future 153 * invocations. 154 */ 155 _targetIsRTL: function(target) { 156 if (this.__targetIsRTL == null) { 157 if (target && target.nodeType !== Node.ELEMENT_NODE) { 158 target = target.host; 159 } 160 161 this.__targetIsRTL = target && 162 window.getComputedStyle(target)['direction'] === 'rtl'; 163 } 164 165 return this.__targetIsRTL; 166 }, 167 168 /** 169 * 170 * When name is changed, register iconset metadata 171 * 172 */ 173 _nameChanged: function() { 174 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); 175 this.async(function() { 176 this.fire('iron-iconset-added', this, {node: window}); 177 }); 178 }, 179 180 /** 181 * Create a map of child SVG elements by id. 182 * 183 * @return {!Object} Map of id's to SVG elements. 184 */ 185 _createIconMap: function() { 186 // Objects chained to Object.prototype (`{}`) have members. Specifically, 187 // on FF there is a `watch` method that confuses the icon map, so we 188 // need to use a null-based object here. 189 var icons = Object.create(null); 190 Polymer.dom(this).querySelectorAll('[id]') 191 .forEach(function(icon) { 192 icons[icon.id] = icon; 193 }); 194 return icons; 195 }, 196 197 /** 198 * Produce installable clone of the SVG element matching `id` in this 199 * iconset, or `undefined` if there is no matching element. 200 * 201 * @return {Element} Returns an installable clone of the SVG element 202 * matching `id`. 203 */ 204 _cloneIcon: function(id, mirrorAllowed) { 205 // create the icon map on-demand, since the iconset itself has no discrete 206 // signal to know when it's children are fully parsed 207 this._icons = this._icons || this._createIconMap(); 208 return this._prepareSvgClone(this._icons[id], this.size, mirrorAllowed); 209 }, 210 211 /** 212 * @param {Element} sourceSvg 213 * @param {number} size 214 * @param {Boolean} mirrorAllowed 215 * @return {Element} 216 */ 217 _prepareSvgClone: function(sourceSvg, size, mirrorAllowed) { 218 if (sourceSvg) { 219 var content = sourceSvg.cloneNode(true), 220 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), 221 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + size, 222 cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;'; 223 224 if (mirrorAllowed && content.hasAttribute('mirror-in-rtl')) { 225 cssText += '-webkit-transform:scale(-1,1);transform:scale(-1,1);'; 226 } 227 228 svg.setAttribute('viewBox', viewBox); 229 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); 230 svg.setAttribute('focusable', 'false'); 231 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/370136 232 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root 233 svg.style.cssText = cssText; 234 svg.appendChild(content).removeAttribute('id'); 235 return svg; 236 } 237 return null; 238 } 239 240 }); 241 242</script> 243