You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

934 lines
33 KiB

7 years ago
  1. /*!
  2. * Angular Material Design
  3. * https://github.com/angular/material
  4. * @license MIT
  5. * v1.1.3
  6. */
  7. (function( window, angular, undefined ){
  8. "use strict";
  9. /**
  10. * @ngdoc module
  11. * @name material.components.icon
  12. * @description
  13. * Icon
  14. */
  15. angular.module('material.components.icon', ['material.core']);
  16. angular
  17. .module('material.components.icon')
  18. .directive('mdIcon', ['$mdIcon', '$mdTheming', '$mdAria', '$sce', mdIconDirective]);
  19. /**
  20. * @ngdoc directive
  21. * @name mdIcon
  22. * @module material.components.icon
  23. *
  24. * @restrict E
  25. *
  26. * @description
  27. * The `md-icon` directive makes it easier to use vector-based icons in your app (as opposed to
  28. * raster-based icons types like PNG). The directive supports both icon fonts and SVG icons.
  29. *
  30. * Icons should be considered view-only elements that should not be used directly as buttons; instead nest a `<md-icon>`
  31. * inside a `md-button` to add hover and click features.
  32. *
  33. * ### Icon fonts
  34. * Icon fonts are a technique in which you use a font where the glyphs in the font are
  35. * your icons instead of text. Benefits include a straightforward way to bundle everything into a
  36. * single HTTP request, simple scaling, easy color changing, and more.
  37. *
  38. * `md-icon` lets you consume an icon font by letting you reference specific icons in that font
  39. * by name rather than character code.
  40. *
  41. * ### SVG
  42. * For SVGs, the problem with using `<img>` or a CSS `background-image` is that you can't take
  43. * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG
  44. * animation.
  45. *
  46. * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `<svg>` element in the
  47. * document. The most straightforward way of referencing an SVG icon is via URL, just like a
  48. * traditional `<img>`. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can
  49. * reference it by name instead of URL throughout your templates.
  50. *
  51. * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle
  52. * your SVG icons together and pre-load them with $mdIconProvider as an icon set. An icon set can
  53. * also be given a name, which acts as a namespace for individual icons, so you can reference them
  54. * like `"social:cake"`.
  55. *
  56. * When using SVGs, both external SVGs (via URLs) or sets of SVGs [from icon sets] can be
  57. * easily loaded and used. When using font-icons, developers must follow three (3) simple steps:
  58. *
  59. * <ol>
  60. * <li>Load the font library. e.g.<br/>
  61. * `<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
  62. * rel="stylesheet">`
  63. * </li>
  64. * <li>
  65. * Use either (a) font-icon class names or (b) a fontset and a font ligature to render the font glyph by
  66. * using its textual name _or_ numerical character reference. Note that `material-icons` is the default fontset when
  67. * none is specified.
  68. * </li>
  69. * <li> Use any of the following templates: <br/>
  70. * <ul>
  71. * <li>`<md-icon md-font-icon="classname"></md-icon>`</li>
  72. * <li>`<md-icon md-font-set="font library classname or alias">textual_name</md-icon>`</li>
  73. * <li>`<md-icon> numerical_character_reference </md-icon>`</li>
  74. * <li>`<md-icon ng_bind="'textual_name'"></md-icon>`</li>
  75. * <li>`<md-icon ng-bind="scopeVariable"></md-icon>`</li>
  76. * </ul>
  77. * </li>
  78. * </ol>
  79. *
  80. * Full details for these steps can be found:
  81. *
  82. * <ul>
  83. * <li>http://google.github.io/material-design-icons/</li>
  84. * <li>http://google.github.io/material-design-icons/#icon-font-for-the-web</li>
  85. * </ul>
  86. *
  87. * The Material Design icon style <code>.material-icons</code> and the icon font references are published in
  88. * Material Design Icons:
  89. *
  90. * <ul>
  91. * <li>https://design.google.com/icons/</li>
  92. * <li>https://design.google.com/icons/#ic_accessibility</li>
  93. * </ul>
  94. *
  95. * ### Localization
  96. *
  97. * Because an `md-icon` element's text content is not intended to translated, it is recommended to declare the text
  98. * content for an `md-icon` element in its start tag. Instead of using the HTML text content, consider using `ng-bind`
  99. * with a scope variable or literal string.
  100. *
  101. * Examples:
  102. *
  103. * <ul>
  104. * <li>`<md-icon ng-bind="myIconVariable"></md-icon>`</li>
  105. * <li>`<md-icon ng-bind="'menu'"></md-icon>`
  106. * </ul>
  107. *
  108. * <h2 id="material_design_icons">Material Design Icons</h2>
  109. * Using the Material Design Icon-Selector, developers can easily and quickly search for a Material Design font-icon and
  110. * determine its textual name and character reference code. Click on any icon to see the slide-up information
  111. * panel with details regarding a SVG download or information on the font-icon usage.
  112. *
  113. * <a href="https://www.google.com/design/icons/#ic_accessibility" target="_blank" style="border-bottom:none;">
  114. * <img src="https://cloud.githubusercontent.com/assets/210413/7902490/fe8dd14c-0780-11e5-98fb-c821cc6475e6.png"
  115. * aria-label="Material Design Icon-Selector" style="max-width:75%;padding-left:10%">
  116. * </a>
  117. *
  118. * <span class="image_caption">
  119. * Click on the image above to link to the
  120. * <a href="https://design.google.com/icons/#ic_accessibility" target="_blank">Material Design Icon-Selector</a>.
  121. * </span>
  122. *
  123. * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used
  124. * to render the icon. Requires the fonts and the named CSS styles to be preloaded.
  125. * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
  126. * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
  127. * internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
  128. * @param {string} md-svg-src String URL (or expression) used to load, cache, and display an
  129. * external SVG.
  130. * @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal cache;
  131. * interpolated strings or expressions may also be used. Specific set names can be used with
  132. * the syntax `<set name>:<icon name>`.<br/><br/>
  133. * To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
  134. * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
  135. * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
  136. * nor a label on the parent element, a warning will be logged to the console.
  137. * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon
  138. * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon
  139. * nor a label on the parent element, a warning will be logged to the console.
  140. *
  141. * @usage
  142. * When using SVGs:
  143. * <hljs lang="html">
  144. *
  145. * <!-- Icon ID; may contain optional icon set prefix; icons must registered using $mdIconProvider -->
  146. * <md-icon md-svg-icon="social:android" aria-label="android " ></md-icon>
  147. *
  148. * <!-- Icon urls; may be preloaded in templateCache -->
  149. * <md-icon md-svg-src="/android.svg" aria-label="android " ></md-icon>
  150. * <md-icon md-svg-src="{{ getAndroid() }}" aria-label="android " ></md-icon>
  151. *
  152. * </hljs>
  153. *
  154. * Use the <code>$mdIconProvider</code> to configure your application with
  155. * svg iconsets.
  156. *
  157. * <hljs lang="js">
  158. * angular.module('appSvgIconSets', ['ngMaterial'])
  159. * .controller('DemoCtrl', function($scope) {})
  160. * .config(function($mdIconProvider) {
  161. * $mdIconProvider
  162. * .iconSet('social', 'img/icons/sets/social-icons.svg', 24)
  163. * .defaultIconSet('img/icons/sets/core-icons.svg', 24);
  164. * });
  165. * </hljs>
  166. *
  167. *
  168. * When using Font Icons with classnames:
  169. * <hljs lang="html">
  170. *
  171. * <md-icon md-font-icon="android" aria-label="android" ></md-icon>
  172. * <md-icon class="icon_home" aria-label="Home" ></md-icon>
  173. *
  174. * </hljs>
  175. *
  176. * When using Material Font Icons with ligatures:
  177. * <hljs lang="html">
  178. * <!--
  179. * For Material Design Icons
  180. * The class '.material-icons' is auto-added if a style has NOT been specified
  181. * since `material-icons` is the default fontset. So your markup:
  182. * -->
  183. * <md-icon> face </md-icon>
  184. * <!-- becomes this at runtime: -->
  185. * <md-icon md-font-set="material-icons"> face </md-icon>
  186. * <!-- If the fontset does not support ligature names, then we need to use the ligature unicode.-->
  187. * <md-icon> &#xE87C; </md-icon>
  188. * <!-- The class '.material-icons' must be manually added if other styles are also specified-->
  189. * <md-icon class="material-icons md-light md-48"> face </md-icon>
  190. * </hljs>
  191. *
  192. * When using other Font-Icon libraries:
  193. *
  194. * <hljs lang="js">
  195. * // Specify a font-icon style alias
  196. * angular.config(function($mdIconProvider) {
  197. * $mdIconProvider.fontSet('md', 'material-icons');
  198. * });
  199. * </hljs>
  200. *
  201. * <hljs lang="html">
  202. * <md-icon md-font-set="md">favorite</md-icon>
  203. * </hljs>
  204. *
  205. */
  206. function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
  207. return {
  208. restrict: 'E',
  209. link : postLink
  210. };
  211. /**
  212. * Directive postLink
  213. * Supports embedded SVGs, font-icons, & external SVGs
  214. */
  215. function postLink(scope, element, attr) {
  216. $mdTheming(element);
  217. var lastFontIcon = attr.mdFontIcon;
  218. var lastFontSet = $mdIcon.fontSet(attr.mdFontSet);
  219. prepareForFontIcon();
  220. attr.$observe('mdFontIcon', fontIconChanged);
  221. attr.$observe('mdFontSet', fontIconChanged);
  222. // Keep track of the content of the svg src so we can compare against it later to see if the
  223. // attribute is static (and thus safe).
  224. var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc);
  225. // If using a font-icon, then the textual name of the icon itself
  226. // provides the aria-label.
  227. var label = attr.alt || attr.mdFontIcon || attr.mdSvgIcon || element.text();
  228. var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
  229. if ( !attr['aria-label'] ) {
  230. if (label !== '' && !parentsHaveText() ) {
  231. $mdAria.expect(element, 'aria-label', label);
  232. $mdAria.expect(element, 'role', 'img');
  233. } else if ( !element.text() ) {
  234. // If not a font-icon with ligature, then
  235. // hide from the accessibility layer.
  236. $mdAria.expect(element, 'aria-hidden', 'true');
  237. }
  238. }
  239. if (attrName) {
  240. // Use either pre-configured SVG or URL source, respectively.
  241. attr.$observe(attrName, function(attrVal) {
  242. element.empty();
  243. if (attrVal) {
  244. $mdIcon(attrVal)
  245. .then(function(svg) {
  246. element.empty();
  247. element.append(svg);
  248. });
  249. }
  250. });
  251. }
  252. function parentsHaveText() {
  253. var parent = element.parent();
  254. if (parent.attr('aria-label') || parent.text()) {
  255. return true;
  256. }
  257. else if(parent.parent().attr('aria-label') || parent.parent().text()) {
  258. return true;
  259. }
  260. return false;
  261. }
  262. function prepareForFontIcon() {
  263. if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
  264. if (attr.mdFontIcon) {
  265. element.addClass('md-font ' + attr.mdFontIcon);
  266. }
  267. element.addClass(lastFontSet);
  268. }
  269. }
  270. function fontIconChanged() {
  271. if (!attr.mdSvgIcon && !attr.mdSvgSrc) {
  272. if (attr.mdFontIcon) {
  273. element.removeClass(lastFontIcon);
  274. element.addClass(attr.mdFontIcon);
  275. lastFontIcon = attr.mdFontIcon;
  276. }
  277. var fontSet = $mdIcon.fontSet(attr.mdFontSet);
  278. if (lastFontSet !== fontSet) {
  279. element.removeClass(lastFontSet);
  280. element.addClass(fontSet);
  281. lastFontSet = fontSet;
  282. }
  283. }
  284. }
  285. }
  286. }
  287. MdIconService['$inject'] = ["config", "$templateRequest", "$q", "$log", "$mdUtil", "$sce"];angular
  288. .module('material.components.icon')
  289. .constant('$$mdSvgRegistry', {
  290. 'mdTabsArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=',
  291. 'mdClose': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=',
  292. 'mdCancel': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==',
  293. 'mdMenu': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+',
  294. 'mdToggleArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==',
  295. 'mdCalendar': 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==',
  296. 'mdChecked': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L2c+PC9zdmc+'
  297. })
  298. .provider('$mdIcon', MdIconProvider);
  299. /**
  300. * @ngdoc service
  301. * @name $mdIconProvider
  302. * @module material.components.icon
  303. *
  304. * @description
  305. * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow
  306. * icons and icon sets to be pre-registered and associated with source URLs **before** the `<md-icon />`
  307. * directives are compiled.
  308. *
  309. * If using font-icons, the developer is responsible for loading the fonts.
  310. *
  311. * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded
  312. * internally by the `$mdIcon` service using the `$templateRequest` service. When an SVG is
  313. * requested by name/ID, the `$mdIcon` service searches its registry for the associated source URL;
  314. * that URL is used to on-demand load and parse the SVG dynamically.
  315. *
  316. * The `$templateRequest` service expects the icons source to be loaded over trusted URLs.<br/>
  317. * This means, when loading icons from an external URL, you have to trust the URL in the `$sceDelegateProvider`.
  318. *
  319. * <hljs lang="js">
  320. * app.config(function($sceDelegateProvider) {
  321. * $sceDelegateProvider.resourceUrlWhitelist([
  322. * // Adding 'self' to the whitelist, will allow requests from the current origin.
  323. * 'self',
  324. * // Using double asterisks here, will allow all URLs to load.
  325. * // We recommend to only specify the given domain you want to allow.
  326. * '**'
  327. * ]);
  328. * });
  329. * </hljs>
  330. *
  331. * Read more about the [$sceDelegateProvider](https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider).
  332. *
  333. * **Notice:** Most font-icons libraries do not support ligatures (for example `fontawesome`).<br/>
  334. * In such cases you are not able to use the icon's ligature name - Like so:
  335. *
  336. * <hljs lang="html">
  337. * <md-icon md-font-set="fa">fa-bell</md-icon>
  338. * </hljs>
  339. *
  340. * You should instead use the given unicode, instead of the ligature name.
  341. *
  342. * <p ng-hide="true"> ##// Notice we can't use a hljs element here, because the characters will be escaped.</p>
  343. * ```html
  344. * <md-icon md-font-set="fa">&#xf0f3</md-icon>
  345. * ```
  346. *
  347. * All unicode ligatures are prefixed with the `&#x` string.
  348. *
  349. * @usage
  350. * <hljs lang="js">
  351. * app.config(function($mdIconProvider) {
  352. *
  353. * // Configure URLs for icons specified by [set:]id.
  354. *
  355. * $mdIconProvider
  356. * .defaultFontSet( 'fa' ) // This sets our default fontset className.
  357. * .defaultIconSet('my/app/icons.svg') // Register a default set of SVG icons
  358. * .iconSet('social', 'my/app/social.svg') // Register a named icon set of SVGs
  359. * .icon('android', 'my/app/android.svg') // Register a specific icon (by name)
  360. * .icon('work:chair', 'my/app/chair.svg'); // Register icon in a specific set
  361. * });
  362. * </hljs>
  363. *
  364. * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime
  365. * **startup** process (shown below):
  366. *
  367. * <hljs lang="js">
  368. * app.config(function($mdIconProvider) {
  369. *
  370. * // Register a default set of SVG icon definitions
  371. * $mdIconProvider.defaultIconSet('my/app/icons.svg')
  372. *
  373. * })
  374. * .run(function($templateRequest){
  375. *
  376. * // Pre-fetch icons sources by URL and cache in the $templateCache...
  377. * // subsequent $templateRequest calls will look there first.
  378. *
  379. * var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg'];
  380. *
  381. * angular.forEach(urls, function(url) {
  382. * $templateRequest(url);
  383. * });
  384. *
  385. * });
  386. *
  387. * </hljs>
  388. *
  389. * > <b>Note:</b> The loaded SVG data is subsequently cached internally for future requests.
  390. *
  391. */
  392. /**
  393. * @ngdoc method
  394. * @name $mdIconProvider#icon
  395. *
  396. * @description
  397. * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix.
  398. * These icons will later be retrieved from the cache using `$mdIcon( <icon name> )`
  399. *
  400. * @param {string} id Icon name/id used to register the icon
  401. * @param {string} url specifies the external location for the data file. Used internally by
  402. * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
  403. * was configured.
  404. * @param {number=} viewBoxSize Sets the width and height the icon's viewBox.
  405. * It is ignored for icons with an existing viewBox. Default size is 24.
  406. *
  407. * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
  408. *
  409. * @usage
  410. * <hljs lang="js">
  411. * app.config(function($mdIconProvider) {
  412. *
  413. * // Configure URLs for icons specified by [set:]id.
  414. *
  415. * $mdIconProvider
  416. * .icon('android', 'my/app/android.svg') // Register a specific icon (by name)
  417. * .icon('work:chair', 'my/app/chair.svg'); // Register icon in a specific set
  418. * });
  419. * </hljs>
  420. *
  421. */
  422. /**
  423. * @ngdoc method
  424. * @name $mdIconProvider#iconSet
  425. *
  426. * @description
  427. * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition
  428. * has an icon id. Individual icons can be subsequently retrieved from this cached set using
  429. * `$mdIcon(<icon set name>:<icon name>)`
  430. *
  431. * @param {string} id Icon name/id used to register the iconset
  432. * @param {string} url specifies the external location for the data file. Used internally by
  433. * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
  434. * was configured.
  435. * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
  436. * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
  437. * Default value is 24.
  438. *
  439. * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
  440. *
  441. *
  442. * @usage
  443. * <hljs lang="js">
  444. * app.config(function($mdIconProvider) {
  445. *
  446. * // Configure URLs for icons specified by [set:]id.
  447. *
  448. * $mdIconProvider
  449. * .iconSet('social', 'my/app/social.svg') // Register a named icon set
  450. * });
  451. * </hljs>
  452. *
  453. */
  454. /**
  455. * @ngdoc method
  456. * @name $mdIconProvider#defaultIconSet
  457. *
  458. * @description
  459. * Register a source URL for the default 'named' set of icons. Unless explicitly registered,
  460. * subsequent lookups of icons will failover to search this 'default' icon set.
  461. * Icon can be retrieved from this cached, default set using `$mdIcon(<name>)`
  462. *
  463. * @param {string} url specifies the external location for the data file. Used internally by
  464. * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading
  465. * was configured.
  466. * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set.
  467. * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size.
  468. * Default value is 24.
  469. *
  470. * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
  471. *
  472. * @usage
  473. * <hljs lang="js">
  474. * app.config(function($mdIconProvider) {
  475. *
  476. * // Configure URLs for icons specified by [set:]id.
  477. *
  478. * $mdIconProvider
  479. * .defaultIconSet( 'my/app/social.svg' ) // Register a default icon set
  480. * });
  481. * </hljs>
  482. *
  483. */
  484. /**
  485. * @ngdoc method
  486. * @name $mdIconProvider#defaultFontSet
  487. *
  488. * @description
  489. * When using Font-Icons, Angular Material assumes the the Material Design icons will be used and automatically
  490. * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library
  491. * class style that should be applied to the `<md-icon>`.
  492. *
  493. * Configuring the default means that the attributes
  494. * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the
  495. * `<md-icon>` markup. For example:
  496. *
  497. * `<md-icon> face </md-icon>`
  498. * will render as
  499. * `<span class="material-icons"> face </span>`, and
  500. *
  501. * `<md-icon md-font-set="fa"> face </md-icon>`
  502. * will render as
  503. * `<span class="fa"> face </span>`
  504. *
  505. * @param {string} name of the font-library style that should be applied to the md-icon DOM element
  506. *
  507. * @usage
  508. * <hljs lang="js">
  509. * app.config(function($mdIconProvider) {
  510. * $mdIconProvider.defaultFontSet( 'fa' );
  511. * });
  512. * </hljs>
  513. *
  514. */
  515. /**
  516. * @ngdoc method
  517. * @name $mdIconProvider#fontSet
  518. *
  519. * @description
  520. * When using a font set for `<md-icon>` you must specify the correct font classname in the `md-font-set`
  521. * attribute. If the fonset className is really long, your markup may become cluttered... an easy
  522. * solution is to define an `alias` for your fontset:
  523. *
  524. * @param {string} alias of the specified fontset.
  525. * @param {string} className of the fontset.
  526. *
  527. * @usage
  528. * <hljs lang="js">
  529. * app.config(function($mdIconProvider) {
  530. * // In this case, we set an alias for the `material-icons` fontset.
  531. * $mdIconProvider.fontSet('md', 'material-icons');
  532. * });
  533. * </hljs>
  534. *
  535. */
  536. /**
  537. * @ngdoc method
  538. * @name $mdIconProvider#defaultViewBoxSize
  539. *
  540. * @description
  541. * While `<md-icon />` markup can also be style with sizing CSS, this method configures
  542. * the default width **and** height used for all icons; unless overridden by specific CSS.
  543. * The default sizing is (24px, 24px).
  544. * @param {number=} viewBoxSize Sets the width and height of the viewBox for an icon or an icon set.
  545. * All icons in a set should be the same size. The default value is 24.
  546. *
  547. * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API
  548. *
  549. * @usage
  550. * <hljs lang="js">
  551. * app.config(function($mdIconProvider) {
  552. *
  553. * // Configure URLs for icons specified by [set:]id.
  554. *
  555. * $mdIconProvider
  556. * .defaultViewBoxSize(36) // Register a default icon size (width == height)
  557. * });
  558. * </hljs>
  559. *
  560. */
  561. var config = {
  562. defaultViewBoxSize: 24,
  563. defaultFontSet: 'material-icons',
  564. fontSets: []
  565. };
  566. function MdIconProvider() {
  567. }
  568. MdIconProvider.prototype = {
  569. icon: function(id, url, viewBoxSize) {
  570. if (id.indexOf(':') == -1) id = '$default:' + id;
  571. config[id] = new ConfigurationItem(url, viewBoxSize);
  572. return this;
  573. },
  574. iconSet: function(id, url, viewBoxSize) {
  575. config[id] = new ConfigurationItem(url, viewBoxSize);
  576. return this;
  577. },
  578. defaultIconSet: function(url, viewBoxSize) {
  579. var setName = '$default';
  580. if (!config[setName]) {
  581. config[setName] = new ConfigurationItem(url, viewBoxSize);
  582. }
  583. config[setName].viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
  584. return this;
  585. },
  586. defaultViewBoxSize: function(viewBoxSize) {
  587. config.defaultViewBoxSize = viewBoxSize;
  588. return this;
  589. },
  590. /**
  591. * Register an alias name associated with a font-icon library style ;
  592. */
  593. fontSet: function fontSet(alias, className) {
  594. config.fontSets.push({
  595. alias: alias,
  596. fontSet: className || alias
  597. });
  598. return this;
  599. },
  600. /**
  601. * Specify a default style name associated with a font-icon library
  602. * fallback to Material Icons.
  603. *
  604. */
  605. defaultFontSet: function defaultFontSet(className) {
  606. config.defaultFontSet = !className ? '' : className;
  607. return this;
  608. },
  609. defaultIconSize: function defaultIconSize(iconSize) {
  610. config.defaultIconSize = iconSize;
  611. return this;
  612. },
  613. $get: ['$templateRequest', '$q', '$log', '$mdUtil', '$sce', function($templateRequest, $q, $log, $mdUtil, $sce) {
  614. return MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce);
  615. }]
  616. };
  617. /**
  618. * Configuration item stored in the Icon registry; used for lookups
  619. * to load if not already cached in the `loaded` cache
  620. */
  621. function ConfigurationItem(url, viewBoxSize) {
  622. this.url = url;
  623. this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize;
  624. }
  625. /**
  626. * @ngdoc service
  627. * @name $mdIcon
  628. * @module material.components.icon
  629. *
  630. * @description
  631. * The `$mdIcon` service is a function used to lookup SVG icons.
  632. *
  633. * @param {string} id Query value for a unique Id or URL. If the argument is a URL, then the service will retrieve the icon element
  634. * from its internal cache or load the icon and cache it first. If the value is not a URL-type string, then an ID lookup is
  635. * performed. The Id may be a unique icon ID or may include an iconSet ID prefix.
  636. *
  637. * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
  638. * using the `$mdIconProvider`.
  639. *
  640. * @returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was
  641. * created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise
  642. * will get rejected.
  643. *
  644. * @usage
  645. * <hljs lang="js">
  646. * function SomeDirective($mdIcon) {
  647. *
  648. * // See if the icon has already been loaded, if not
  649. * // then lookup the icon from the registry cache, load and cache
  650. * // it for future requests.
  651. * // NOTE: ID queries require configuration with $mdIconProvider
  652. *
  653. * $mdIcon('android').then(function(iconEl) { element.append(iconEl); });
  654. * $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });
  655. *
  656. * // Load and cache the external SVG using a URL
  657. *
  658. * $mdIcon('img/icons/android.svg').then(function(iconEl) {
  659. * element.append(iconEl);
  660. * });
  661. * };
  662. * </hljs>
  663. *
  664. * > <b>Note:</b> The `<md-icon>` directive internally uses the `$mdIcon` service to query, loaded,
  665. * and instantiate SVG DOM elements.
  666. */
  667. /* ngInject */
  668. function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
  669. var iconCache = {};
  670. var svgCache = {};
  671. var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
  672. var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
  673. Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle};
  674. getIcon.fontSet = findRegisteredFontSet;
  675. // Publish service...
  676. return getIcon;
  677. /**
  678. * Actual $mdIcon service is essentially a lookup function
  679. */
  680. function getIcon(id) {
  681. id = id || '';
  682. // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper
  683. // over a URL string. If the value is not trusted, this will intentionally throw an error
  684. // because the user is attempted to use an unsafe URL, potentially opening themselves up
  685. // to an XSS attack.
  686. if (!angular.isString(id)) {
  687. id = $sce.getTrustedUrl(id);
  688. }
  689. // If already loaded and cached, use a clone of the cached icon.
  690. // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
  691. if (iconCache[id]) {
  692. return $q.when(transformClone(iconCache[id]));
  693. }
  694. if (urlRegex.test(id) || dataUrlRegex.test(id)) {
  695. return loadByURL(id).then(cacheIcon(id));
  696. }
  697. if (id.indexOf(':') == -1) {
  698. id = '$default:' + id;
  699. }
  700. var load = config[id] ? loadByID : loadFromIconSet;
  701. return load(id)
  702. .then(cacheIcon(id));
  703. }
  704. /**
  705. * Lookup registered fontSet style using its alias...
  706. * If not found,
  707. */
  708. function findRegisteredFontSet(alias) {
  709. var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
  710. if (useDefault) return config.defaultFontSet;
  711. var result = alias;
  712. angular.forEach(config.fontSets, function(it) {
  713. if (it.alias == alias) result = it.fontSet || result;
  714. });
  715. return result;
  716. }
  717. function transformClone(cacheElement) {
  718. var clone = cacheElement.clone();
  719. var cacheSuffix = '_cache' + $mdUtil.nextUid();
  720. // We need to modify for each cached icon the id attributes.
  721. // This is needed because SVG id's are treated as normal DOM ids
  722. // and should not have a duplicated id.
  723. if (clone.id) clone.id += cacheSuffix;
  724. angular.forEach(clone.querySelectorAll('[id]'), function(item) {
  725. item.id += cacheSuffix;
  726. });
  727. return clone;
  728. }
  729. /**
  730. * Prepare and cache the loaded icon for the specified `id`
  731. */
  732. function cacheIcon(id) {
  733. return function updateCache(icon) {
  734. iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);
  735. return iconCache[id].clone();
  736. };
  737. }
  738. /**
  739. * Lookup the configuration in the registry, if !registered throw an error
  740. * otherwise load the icon [on-demand] using the registered URL.
  741. *
  742. */
  743. function loadByID(id) {
  744. var iconConfig = config[id];
  745. return loadByURL(iconConfig.url).then(function(icon) {
  746. return new Icon(icon, iconConfig);
  747. });
  748. }
  749. /**
  750. * Loads the file as XML and uses querySelector( <id> ) to find
  751. * the desired node...
  752. */
  753. function loadFromIconSet(id) {
  754. var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
  755. var iconSetConfig = config[setName];
  756. return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet);
  757. function extractFromSet(set) {
  758. var iconName = id.slice(id.lastIndexOf(':') + 1);
  759. var icon = set.querySelector('#' + iconName);
  760. return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id);
  761. }
  762. function announceIdNotFound(id) {
  763. var msg = 'icon ' + id + ' not found';
  764. $log.warn(msg);
  765. return $q.reject(msg || id);
  766. }
  767. }
  768. /**
  769. * Load the icon by URL (may use the $templateCache).
  770. * Extract the data for later conversion to Icon
  771. */
  772. function loadByURL(url) {
  773. /* Load the icon from embedded data URL. */
  774. function loadByDataUrl(url) {
  775. var results = dataUrlRegex.exec(url);
  776. var isBase64 = /base64/i.test(url);
  777. var data = isBase64 ? window.atob(results[2]) : results[2];
  778. return $q.when(angular.element(data)[0]);
  779. }
  780. /* Load the icon by URL using HTTP. */
  781. function loadByHttpUrl(url) {
  782. return $q(function(resolve, reject) {
  783. // Catch HTTP or generic errors not related to incorrect icon IDs.
  784. var announceAndReject = function(err) {
  785. var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
  786. $log.warn(msg);
  787. reject(err);
  788. },
  789. extractSvg = function(response) {
  790. if (!svgCache[url]) {
  791. svgCache[url] = angular.element('<div>').append(response)[0].querySelector('svg');
  792. }
  793. resolve(svgCache[url]);
  794. };
  795. $templateRequest(url, true).then(extractSvg, announceAndReject);
  796. });
  797. }
  798. return dataUrlRegex.test(url)
  799. ? loadByDataUrl(url)
  800. : loadByHttpUrl(url);
  801. }
  802. /**
  803. * Check target signature to see if it is an Icon instance.
  804. */
  805. function isIcon(target) {
  806. return angular.isDefined(target.element) && angular.isDefined(target.config);
  807. }
  808. /**
  809. * Define the Icon class
  810. */
  811. function Icon(el, config) {
  812. if (el && el.tagName != 'svg') {
  813. el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
  814. }
  815. // Inject the namespace if not available...
  816. if (!el.getAttribute('xmlns')) {
  817. el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
  818. }
  819. this.element = el;
  820. this.config = config;
  821. this.prepare();
  822. }
  823. /**
  824. * Prepare the DOM element that will be cached in the
  825. * loaded iconCache store.
  826. */
  827. function prepareAndStyle() {
  828. var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
  829. angular.forEach({
  830. 'fit': '',
  831. 'height': '100%',
  832. 'width': '100%',
  833. 'preserveAspectRatio': 'xMidYMid meet',
  834. 'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize),
  835. 'focusable': false // Disable IE11s default behavior to make SVGs focusable
  836. }, function(val, attr) {
  837. this.element.setAttribute(attr, val);
  838. }, this);
  839. }
  840. /**
  841. * Clone the Icon DOM element.
  842. */
  843. function cloneSVG() {
  844. // If the element or any of its children have a style attribute, then a CSP policy without
  845. // 'unsafe-inline' in the style-src directive, will result in a violation.
  846. return this.element.cloneNode(true);
  847. }
  848. }
  849. })(window, window.angular);