/*! * AngularJS Material Design * https://github.com/angular/material * @license MIT * v1.1.19 */ (function( window, angular, undefined ){ "use strict"; /** * @ngdoc module * @name material.components.icon * @description * Icon */ angular.module('material.components.icon', ['material.core']); angular .module('material.components.icon') .directive('mdIcon', ['$mdIcon', '$mdTheming', '$mdAria', '$sce', mdIconDirective]); /** * @ngdoc directive * @name mdIcon * @module material.components.icon * * @restrict E * * @description * The `md-icon` directive makes it easier to use vector-based icons in your app (as opposed to * raster-based icons types like PNG). The directive supports both icon fonts and SVG icons. * * Icons should be considered view-only elements that should not be used directly as buttons; instead nest a `` * inside a `md-button` to add hover and click features. * * ### Icon fonts * Icon fonts are a technique in which you use a font where the glyphs in the font are * your icons instead of text. Benefits include a straightforward way to bundle everything into a * single HTTP request, simple scaling, easy color changing, and more. * * `md-icon` lets you consume an icon font by letting you reference specific icons in that font * by name rather than character code. * * When using font-icons, developers must follow three (3) simple steps: * *
    *
  1. Load the font library. e.g.
    * `` *
  2. *
  3. * Use either (a) font-icon class names or (b) a fontset and a font ligature to render the font glyph by * using its textual name _or_ numerical character reference. Note that `material-icons` is the default fontset when * none is specified. *
  4. *
  5. Use any of the following templates:
    *
      *
    • ``
    • *
    • `textual_name`
    • *
    • ` numerical_character_reference `
    • *
    • ``
    • *
    • ``
    • *
    *
  6. *
* * Full details for these steps can be found in the * * Material Design Icon font for the web docs. * * You can browse and search the Material Design icon style .material-icons * in the Material Design Icons tool. * * ### SVG * For SVGs, the problem with using `` or a CSS `background-image` is that you can't take * advantage of some SVG features, such as styling specific parts of the icon with CSS or SVG * animation. * * `md-icon` makes it easier to use SVG icons by *inlining* the SVG into an `` element in the * document. The most straightforward way of referencing an SVG icon is via URL, just like a * traditional ``. `$mdIconProvider`, as a convenience, lets you _name_ an icon so you can * reference it by name instead of URL throughout your templates. * * Additionally, you may not want to make separate HTTP requests for every icon, so you can bundle * your SVG icons together and pre-load them with `$mdIconProvider` as an icon set. An icon set can * also be given a name, which acts as a namespace for individual icons, so you can reference them * like `"social:cake"`. * * When using SVGs, both external SVGs (via URLs) or sets of SVGs (from icon sets) can be * easily loaded and used. * * ### Localization * * Because an `md-icon` element's text content is not intended to be translated, it is recommended * to declare the text content for an `md-icon` element in its start tag. Instead of using the HTML * text content, consider using `ng-bind` with a scope variable or literal string. * * Examples: * * * *

Material Design Icons tool

* Using the Material Design Icons tool, developers can easily and quickly search for a specific * open source Material Design icon. The search is in the top left. Below search, you can select * from the new icon themes or filter by icon category. * * * * * *
* Click on the image above to open the * Material Design Icons tool. *
* * Click on any icon, then click on the "Selected Icon" chevron to see the slide-up * information panel with details regarding a SVG download and information on the font-icon's * textual name. This panel also allows you to select a black on transparent or white on transparent * icon and to change the icon size. These settings only affect the downloaded icons. * * @param {string} md-font-icon String name of CSS icon associated with the font-face will be used * to render the icon. Requires the fonts and the named CSS styles to be preloaded. * @param {string} md-font-set CSS style name associated with the font library; which will be assigned as * the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname; * internally use `$mdIconProvider.fontSet()` to determine the style name. * @param {string} md-svg-src String URL (or expression) used to load, cache, and display an * external SVG. * @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal cache; * interpolated strings or expressions may also be used. Specific set names can be used with * the syntax `:`.

* To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service. * @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon * nor a label on the parent element, a warning will be logged to the console. * @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon * will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon * nor a label on the parent element, a warning will be logged to the console. * * @usage * When using SVGs: * * * * * * * * * * * * Use the $mdIconProvider to configure your application with * SVG icon sets. * * * angular.module('appSvgIconSets', ['ngMaterial']) * .controller('DemoCtrl', function($scope) {}) * .config(function($mdIconProvider) { * $mdIconProvider * .iconSet('social', 'img/icons/sets/social-icons.svg', 24) * .defaultIconSet('img/icons/sets/core-icons.svg', 24); * }); * * * * When using Font Icons with classnames: * * * * * * * * When using Material Font Icons with ligatures: * * * face * * face * * * * face * * * When using other Font-Icon libraries: * * * // Specify a font-icon style alias * angular.config(function($mdIconProvider) { * $mdIconProvider.fontSet('md', 'material-icons'); * }); * * * * favorite * * */ function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) { return { restrict: 'E', link : postLink }; /** * Directive postLink * Supports embedded SVGs, font-icons, & external SVGs */ function postLink(scope, element, attr) { $mdTheming(element); var lastFontIcon = attr.mdFontIcon; var lastFontSet = $mdIcon.fontSet(attr.mdFontSet); prepareForFontIcon(); attr.$observe('mdFontIcon', fontIconChanged); attr.$observe('mdFontSet', fontIconChanged); // Keep track of the content of the svg src so we can compare against it later to see if the // attribute is static (and thus safe). var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc); // If using a font-icon, then the textual name of the icon itself // provides the aria-label. var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || ''); /* Provide a default accessibility role of img */ if (!attr.role) { $mdAria.expect(element, 'role', 'img'); /* manually update attr variable */ attr.role = 'img'; } /* Don't process ARIA if already valid */ if (attr.role === "img" && !attr.ariaHidden && !$mdAria.hasAriaLabel(element)) { var iconName; if (attr.alt) { /* Use alt text by default if available */ $mdAria.expect(element, 'aria-label', attr.alt); } else if ($mdAria.parentHasAriaLabel(element, 2)) { /* Parent has ARIA so we will assume it will describe the image */ $mdAria.expect(element, 'aria-hidden', 'true'); } else if (iconName = (attr.mdFontIcon || attr.mdSvgIcon || element.text())) { /* Use icon name as aria-label */ $mdAria.expect(element, 'aria-label', iconName); } else { /* No label found */ $mdAria.expect(element, 'aria-hidden', 'true'); } } if (attrName) { // Use either pre-configured SVG or URL source, respectively. attr.$observe(attrName, function(attrVal) { element.empty(); if (attrVal) { $mdIcon(attrVal) .then(function(svg) { element.empty(); element.append(svg); }); } }); } function prepareForFontIcon() { if (!attr.mdSvgIcon && !attr.mdSvgSrc) { if (attr.mdFontIcon) { element.addClass('md-font ' + attr.mdFontIcon); } element.addClass(lastFontSet); } } function fontIconChanged() { if (!attr.mdSvgIcon && !attr.mdSvgSrc) { if (attr.mdFontIcon) { element.removeClass(lastFontIcon); element.addClass(attr.mdFontIcon); lastFontIcon = attr.mdFontIcon; } var fontSet = $mdIcon.fontSet(attr.mdFontSet); if (lastFontSet !== fontSet) { element.removeClass(lastFontSet); element.addClass(fontSet); lastFontSet = fontSet; } } } } } MdIconService['$inject'] = ["config", "$templateRequest", "$q", "$log", "$mdUtil", "$sce"];angular .module('material.components.icon') .constant('$$mdSvgRegistry', { 'mdTabsArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=', 'mdClose': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=', 'mdCancel': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==', 'mdMenu': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+', 'mdToggleArrow': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==', 'mdCalendar': 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==', 'mdChecked': 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L2c+PC9zdmc+' }) .provider('$mdIcon', MdIconProvider); /** * @ngdoc service * @name $mdIconProvider * @module material.components.icon * * @description * `$mdIconProvider` is used only to register icon IDs with URLs. These configuration features allow * icons and icon sets to be pre-registered and associated with source URLs **before** the `` * directives are compiled. * * If using font-icons, the developer is responsible for loading the fonts. * * If using SVGs, loading of the actual svg files are deferred to on-demand requests and are loaded * internally by the `$mdIcon` service using the `$templateRequest` service. When an SVG is * requested by name/ID, the `$mdIcon` service searches its registry for the associated source URL; * that URL is used to on-demand load and parse the SVG dynamically. * * The `$templateRequest` service expects the icons source to be loaded over trusted URLs.
* This means, when loading icons from an external URL, you have to trust the URL in the `$sceDelegateProvider`. * * * app.config(function($sceDelegateProvider) { * $sceDelegateProvider.resourceUrlWhitelist([ * // Adding 'self' to the whitelist, will allow requests from the current origin. * 'self', * // Using double asterisks here, will allow all URLs to load. * // We recommend to only specify the given domain you want to allow. * '**' * ]); * }); * * * Read more about the [$sceDelegateProvider](https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider). * * **Notice:** Most font-icons libraries do not support ligatures (for example `fontawesome`).
* In such cases you are not able to use the icon's ligature name - Like so: * * * fa-bell * * * You should instead use the given unicode, instead of the ligature name. * *

##// Notice we can't use a hljs element here, because the characters will be escaped.

* ```html * * ``` * * All unicode ligatures are prefixed with the `&#x` string. * * @usage * * app.config(function($mdIconProvider) { * * // Configure URLs for icons specified by [set:]id. * * $mdIconProvider * .defaultFontSet( 'fa' ) // This sets our default fontset className. * .defaultIconSet('my/app/icons.svg') // Register a default set of SVG icons * .iconSet('social', 'my/app/social.svg') // Register a named icon set of SVGs * .icon('android', 'my/app/android.svg') // Register a specific icon (by name) * .icon('work:chair', 'my/app/chair.svg'); // Register icon in a specific set * }); * * * SVG icons and icon sets can be easily pre-loaded and cached using either (a) a build process or (b) a runtime * **startup** process (shown below): * * * app.config(function($mdIconProvider) { * * // Register a default set of SVG icon definitions * $mdIconProvider.defaultIconSet('my/app/icons.svg') * * }) * .run(function($templateRequest){ * * // Pre-fetch icons sources by URL and cache in the $templateCache... * // subsequent $templateRequest calls will look there first. * * var urls = [ 'imy/app/icons.svg', 'img/icons/android.svg']; * * angular.forEach(urls, function(url) { * $templateRequest(url); * }); * * }); * * * * > Note: The loaded SVG data is subsequently cached internally for future requests. * */ /** * @ngdoc method * @name $mdIconProvider#icon * * @description * Register a source URL for a specific icon name; the name may include optional 'icon set' name prefix. * These icons will later be retrieved from the cache using `$mdIcon( )` * * @param {string} id Icon name/id used to register the icon * @param {string} url specifies the external location for the data file. Used internally by * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading * was configured. * @param {number=} viewBoxSize Sets the width and height the icon's viewBox. * It is ignored for icons with an existing viewBox. Default size is 24. * * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API * * @usage * * app.config(function($mdIconProvider) { * * // Configure URLs for icons specified by [set:]id. * * $mdIconProvider * .icon('android', 'my/app/android.svg') // Register a specific icon (by name) * .icon('work:chair', 'my/app/chair.svg'); // Register icon in a specific set * }); * * */ /** * @ngdoc method * @name $mdIconProvider#iconSet * * @description * Register a source URL for a 'named' set of icons; group of SVG definitions where each definition * has an icon id. Individual icons can be subsequently retrieved from this cached set using * `$mdIcon(:)` * * @param {string} id Icon name/id used to register the iconset * @param {string} url specifies the external location for the data file. Used internally by * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading * was configured. * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set. * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size. * Default value is 24. * * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API * * * @usage * * app.config(function($mdIconProvider) { * * // Configure URLs for icons specified by [set:]id. * * $mdIconProvider * .iconSet('social', 'my/app/social.svg') // Register a named icon set * }); * * */ /** * @ngdoc method * @name $mdIconProvider#defaultIconSet * * @description * Register a source URL for the default 'named' set of icons. Unless explicitly registered, * subsequent lookups of icons will failover to search this 'default' icon set. * Icon can be retrieved from this cached, default set using `$mdIcon()` * * @param {string} url specifies the external location for the data file. Used internally by * `$templateRequest` to load the data or as part of the lookup in `$templateCache` if pre-loading * was configured. * @param {number=} viewBoxSize Sets the width and height of the viewBox of all icons in the set. * It is ignored for icons with an existing viewBox. All icons in the icon set should be the same size. * Default value is 24. * * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API * * @usage * * app.config(function($mdIconProvider) { * * // Configure URLs for icons specified by [set:]id. * * $mdIconProvider * .defaultIconSet( 'my/app/social.svg' ) // Register a default icon set * }); * * */ /** * @ngdoc method * @name $mdIconProvider#defaultFontSet * * @description * When using Font-Icons, AngularJS Material assumes the the Material Design icons will be used and automatically * configures the default font-set == 'material-icons'. Note that the font-set references the font-icon library * class style that should be applied to the ``. * * Configuring the default means that the attributes * `md-font-set="material-icons"` or `class="material-icons"` do not need to be explicitly declared on the * `` markup. For example: * * ` face ` * will render as * ` face `, and * * ` face ` * will render as * ` face ` * * @param {string} name of the font-library style that should be applied to the md-icon DOM element * * @usage * * app.config(function($mdIconProvider) { * $mdIconProvider.defaultFontSet( 'fa' ); * }); * * */ /** * @ngdoc method * @name $mdIconProvider#fontSet * * @description * When using a font set for `` you must specify the correct font classname in the `md-font-set` * attribute. If the fonset className is really long, your markup may become cluttered... an easy * solution is to define an `alias` for your fontset: * * @param {string} alias of the specified fontset. * @param {string} className of the fontset. * * @usage * * app.config(function($mdIconProvider) { * // In this case, we set an alias for the `material-icons` fontset. * $mdIconProvider.fontSet('md', 'material-icons'); * }); * * */ /** * @ngdoc method * @name $mdIconProvider#defaultViewBoxSize * * @description * While `` markup can also be style with sizing CSS, this method configures * the default width **and** height used for all icons; unless overridden by specific CSS. * The default sizing is (24px, 24px). * @param {number=} viewBoxSize Sets the width and height of the viewBox for an icon or an icon set. * All icons in a set should be the same size. The default value is 24. * * @returns {obj} an `$mdIconProvider` reference; used to support method call chains for the API * * @usage * * app.config(function($mdIconProvider) { * * // Configure URLs for icons specified by [set:]id. * * $mdIconProvider * .defaultViewBoxSize(36) // Register a default icon size (width == height) * }); * * */ var config = { defaultViewBoxSize: 24, defaultFontSet: 'material-icons', fontSets: [] }; function MdIconProvider() { } MdIconProvider.prototype = { icon: function(id, url, viewBoxSize) { if (id.indexOf(':') == -1) id = '$default:' + id; config[id] = new ConfigurationItem(url, viewBoxSize); return this; }, iconSet: function(id, url, viewBoxSize) { config[id] = new ConfigurationItem(url, viewBoxSize); return this; }, defaultIconSet: function(url, viewBoxSize) { var setName = '$default'; if (!config[setName]) { config[setName] = new ConfigurationItem(url, viewBoxSize); } config[setName].viewBoxSize = viewBoxSize || config.defaultViewBoxSize; return this; }, defaultViewBoxSize: function(viewBoxSize) { config.defaultViewBoxSize = viewBoxSize; return this; }, /** * Register an alias name associated with a font-icon library style ; */ fontSet: function fontSet(alias, className) { config.fontSets.push({ alias: alias, fontSet: className || alias }); return this; }, /** * Specify a default style name associated with a font-icon library * fallback to Material Icons. * */ defaultFontSet: function defaultFontSet(className) { config.defaultFontSet = !className ? '' : className; return this; }, defaultIconSize: function defaultIconSize(iconSize) { config.defaultIconSize = iconSize; return this; }, $get: ['$templateRequest', '$q', '$log', '$mdUtil', '$sce', function($templateRequest, $q, $log, $mdUtil, $sce) { return MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce); }] }; /** * Configuration item stored in the Icon registry; used for lookups * to load if not already cached in the `loaded` cache * @param {string} url * @param {=number} viewBoxSize * @constructor */ function ConfigurationItem(url, viewBoxSize) { this.url = url; this.viewBoxSize = viewBoxSize || config.defaultViewBoxSize; } /** * @ngdoc service * @name $mdIcon * @module material.components.icon * * @description * The `$mdIcon` service is a function used to lookup SVG icons. * * @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 * 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 * performed. The Id may be a unique icon ID or may include an iconSet ID prefix. * * For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured * using the `$mdIconProvider`. * * @returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was * created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise * will get rejected. * * @usage * * function SomeDirective($mdIcon) { * * // See if the icon has already been loaded, if not * // then lookup the icon from the registry cache, load and cache * // it for future requests. * // NOTE: ID queries require configuration with $mdIconProvider * * $mdIcon('android').then(function(iconEl) { element.append(iconEl); }); * $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); }); * * // Load and cache the external SVG using a URL * * $mdIcon('img/icons/android.svg').then(function(iconEl) { * element.append(iconEl); * }); * }; * * * > Note: The `` directive internally uses the `$mdIcon` service to query, load, * and instantiate SVG DOM elements. */ /* ngInject */ function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) { var iconCache = {}; var svgCache = {}; var urlRegex = /[-\w@:%+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%+.~#?&//=]*)?/i; var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-=]*?(base64)?,(.*)$/i; Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle}; getIcon.fontSet = findRegisteredFontSet; // Publish service... return getIcon; /** * Actual $mdIcon service is essentially a lookup function * @param {*} id $sce trust wrapper over a URL string, URL, icon registry id, or icon set id * @returns {angular.$q.Promise} */ function getIcon(id) { id = id || ''; // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper // over a URL string. If the value is not trusted, this will intentionally throw an error // because the user is attempted to use an unsafe URL, potentially opening themselves up // to an XSS attack. if (!angular.isString(id)) { id = $sce.getTrustedUrl(id); } // If already loaded and cached, use a clone of the cached icon. // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache. if (iconCache[id]) { return $q.when(transformClone(iconCache[id])); } if (urlRegex.test(id) || dataUrlRegex.test(id)) { return loadByURL(id).then(cacheIcon(id)); } if (id.indexOf(':') === -1) { id = '$default:' + id; } var load = config[id] ? loadByID : loadFromIconSet; return load(id) .then(cacheIcon(id)); } /** * Lookup a registered fontSet style using its alias. * @param {string} alias used to lookup the alias in the array of fontSets * @returns {*} matching fontSet or the defaultFontSet if that alias does not match */ function findRegisteredFontSet(alias) { var useDefault = angular.isUndefined(alias) || !(alias && alias.length); if (useDefault) { return config.defaultFontSet; } var result = alias; angular.forEach(config.fontSets, function(fontSet) { if (fontSet.alias === alias) { result = fontSet.fontSet || result; } }); return result; } /** * @param {!Icon} cacheElement cached icon from the iconCache * @returns {Icon} cloned Icon element with unique ids */ function transformClone(cacheElement) { var clone = cacheElement.clone(); var newUid = $mdUtil.nextUid(); var cacheSuffix, svgUrlQuerySelector, i, xlinkHrefValue; // These are SVG attributes that can reference element ids. var svgUrlAttributes = [ 'clip-path', 'color-profile', 'cursor', 'fill', 'filter', 'href', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'stroke', 'style', 'vector-effect' ]; var isIeSvg = clone.innerHTML === undefined; // Verify that the newUid only contains a number and not some XSS content. if (!isFinite(Number(newUid))) { throw new Error('Unsafe and unexpected non-number result from $mdUtil.nextUid().'); } cacheSuffix = '_cache' + newUid; // For each cached icon, we need to modify the id attributes and references. // This is needed because SVG ids are treated as normal DOM ids and should not be duplicated on // the page. if (clone.id) { clone.id += cacheSuffix; } // Do as much as possible with querySelectorAll as it provides much greater performance // than RegEx against serialized DOM. angular.forEach(clone.querySelectorAll('[id]'), function(descendantElem) { svgUrlQuerySelector = ''; for (i = 0; i < svgUrlAttributes.length; i++) { svgUrlQuerySelector += '[' + svgUrlAttributes[i] + '="url(#' + descendantElem.id + ')"]'; if (i + 1 < svgUrlAttributes.length) { svgUrlQuerySelector += ', '; } } // Append the cacheSuffix to references of the element's id within url(#id) calls. angular.forEach(clone.querySelectorAll(svgUrlQuerySelector), function(refItem) { updateSvgIdReferences(descendantElem, refItem, isIeSvg, newUid); }); // Handle usages of url(#id) in the SVG's stylesheets angular.forEach(clone.querySelectorAll('style'), function(refItem) { updateSvgIdReferences(descendantElem, refItem, isIeSvg, newUid); }); // Update ids referenced by the deprecated (in SVG v2) xlink:href XML attribute. The now // preferred href attribute is handled above. However, this non-standard XML namespaced // attribute cannot be handled in the same way. Explanation of this query selector here: // https://stackoverflow.com/q/23034283/633107. angular.forEach(clone.querySelectorAll('[*|href]:not([href])'), function(refItem) { xlinkHrefValue = refItem.getAttribute('xlink:href'); if (xlinkHrefValue) { xlinkHrefValue = xlinkHrefValue.replace("#" + descendantElem.id, "#" + descendantElem.id + cacheSuffix); refItem.setAttribute('xlink:href', xlinkHrefValue); } }); descendantElem.id += cacheSuffix; }); return clone; } /** * @param {Element} referencedElement element w/ id that needs to be updated * @param {Element} referencingElement element that references the original id * @param {boolean} isIeSvg true if we're dealing with an SVG in IE11, false otherwise * @param {string} newUid the cache id to add as part of the cache suffix */ function updateSvgIdReferences(referencedElement, referencingElement, isIeSvg, newUid) { var svgElement, cacheSuffix; // Verify that the newUid only contains a number and not some XSS content. if (!isFinite(Number(newUid))) { throw new Error('Unsafe and unexpected non-number result for newUid.'); } cacheSuffix = '_cache' + newUid; // outerHTML of SVG elements is not supported by IE11 if (isIeSvg) { svgElement = $mdUtil.getOuterHTML(referencingElement); svgElement = svgElement.replace("url(#" + referencedElement.id + ")", "url(#" + referencedElement.id + cacheSuffix + ")"); referencingElement.textContent = angular.element(svgElement)[0].innerHTML; } else { // This use of outerHTML should be safe from XSS attack since we are only injecting the // cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above. referencingElement.outerHTML = referencingElement.outerHTML.replace( "url(#" + referencedElement.id + ")", "url(#" + referencedElement.id + cacheSuffix + ")"); } } /** * Prepare and cache the loaded icon for the specified `id`. * @param {string} id icon cache id * @returns {function(*=): *} */ function cacheIcon(id) { return function updateCache(icon) { iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]); return transformClone(iconCache[id]); }; } /** * Lookup the configuration in the registry, if !registered throw an error * otherwise load the icon [on-demand] using the registered URL. * @param {string} id icon registry id * @returns {angular.$q.Promise} */ function loadByID(id) { var iconConfig = config[id]; return loadByURL(iconConfig.url).then(function(icon) { return new Icon(icon, iconConfig); }); } /** * Loads the file as XML and uses querySelector( ) to find the desired node... * @param {string} id icon id in icon set * @returns {angular.$q.Promise} */ function loadFromIconSet(id) { var setName = id.substring(0, id.lastIndexOf(':')) || '$default'; var iconSetConfig = config[setName]; return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet); function extractFromSet(set) { var iconName = id.slice(id.lastIndexOf(':') + 1); var icon = set.querySelector('#' + iconName); return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id); } function announceIdNotFound(id) { var msg = 'icon ' + id + ' not found'; $log.warn(msg); return $q.reject(msg || id); } } /** * Load the icon by URL (may use the $templateCache). * Extract the data for later conversion to Icon * @param {string} url icon URL * @returns {angular.$q.Promise} */ function loadByURL(url) { /* Load the icon from embedded data URL. */ function loadByDataUrl(url) { var results = dataUrlRegex.exec(url); var isBase64 = /base64/i.test(url); var data = isBase64 ? window.atob(results[2]) : results[2]; return $q.when(angular.element(data)[0]); } /* Load the icon by URL using HTTP. */ function loadByHttpUrl(url) { return $q(function(resolve, reject) { // Catch HTTP or generic errors not related to incorrect icon IDs. var announceAndReject = function(err) { var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText); $log.warn(msg); reject(err); }, extractSvg = function(response) { if (!svgCache[url]) { svgCache[url] = angular.element('
').append(response)[0].querySelector('svg'); } resolve(svgCache[url]); }; $templateRequest(url, true).then(extractSvg, announceAndReject); }); } return dataUrlRegex.test(url) ? loadByDataUrl(url) : loadByHttpUrl(url); } /** * Check target signature to see if it is an Icon instance. * @param {Icon|Element} target * @returns {boolean} true if the specified target is an Icon object, false otherwise. */ function isIcon(target) { return angular.isDefined(target.element) && angular.isDefined(target.config); } /** * Define the Icon class * @param {Element} el * @param {=ConfigurationItem} config * @constructor */ function Icon(el, config) { // If the node is a , it won't be rendered so we have to convert it into . if (el && el.tagName.toLowerCase() === 'symbol') { var viewbox = el.getAttribute('viewBox'); // // Check if innerHTML is supported as IE11 does not support innerHTML on SVG elements. if (el.innerHTML) { el = angular.element('') .html(el.innerHTML)[0]; } else { el = angular.element('') .append($mdUtil.getInnerHTML(el))[0]; } if (viewbox) el.setAttribute('viewBox', viewbox); } if (el && el.tagName.toLowerCase() !== 'svg') { el = angular.element( '').append(el.cloneNode(true))[0]; } // Inject the namespace if not available... if (!el.getAttribute('xmlns')) { el.setAttribute('xmlns', "http://www.w3.org/2000/svg"); } this.element = el; this.config = config; this.prepare(); } /** * Prepare the DOM element that will be cached in the * loaded iconCache store. */ function prepareAndStyle() { var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize; angular.forEach({ 'fit': '', 'height': '100%', 'width': '100%', 'preserveAspectRatio': 'xMidYMid meet', 'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize), 'focusable': false // Disable IE11s default behavior to make SVGs focusable }, function(val, attr) { this.element.setAttribute(attr, val); }, this); } /** * Clone the Icon DOM element. */ function cloneSVG() { // If the element or any of its children have a style attribute, then a CSP policy without // 'unsafe-inline' in the style-src directive, will result in a violation. return this.element.cloneNode(true); } } })(window, window.angular);