[6a3a178] | 1 | import * as i0 from '@angular/core';
|
---|
| 2 | import { SecurityContext, Injectable, Optional, Inject, ErrorHandler, SkipSelf, InjectionToken, inject, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, Attribute, Input, NgModule } from '@angular/core';
|
---|
| 3 | import { mixinColor, MatCommonModule } from '@angular/material/core';
|
---|
| 4 | import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
---|
| 5 | import * as i3 from '@angular/common';
|
---|
| 6 | import { DOCUMENT } from '@angular/common';
|
---|
| 7 | import { of, throwError, forkJoin, Subscription } from 'rxjs';
|
---|
| 8 | import { tap, map, catchError, finalize, share, take } from 'rxjs/operators';
|
---|
| 9 | import * as i1 from '@angular/common/http';
|
---|
| 10 | import { HttpClient } from '@angular/common/http';
|
---|
| 11 | import * as i2 from '@angular/platform-browser';
|
---|
| 12 | import { DomSanitizer } from '@angular/platform-browser';
|
---|
| 13 |
|
---|
| 14 | /**
|
---|
| 15 | * @license
|
---|
| 16 | * Copyright Google LLC All Rights Reserved.
|
---|
| 17 | *
|
---|
| 18 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 19 | * found in the LICENSE file at https://angular.io/license
|
---|
| 20 | */
|
---|
| 21 | /**
|
---|
| 22 | * Returns an exception to be thrown in the case when attempting to
|
---|
| 23 | * load an icon with a name that cannot be found.
|
---|
| 24 | * @docs-private
|
---|
| 25 | */
|
---|
| 26 | function getMatIconNameNotFoundError(iconName) {
|
---|
| 27 | return Error(`Unable to find icon with the name "${iconName}"`);
|
---|
| 28 | }
|
---|
| 29 | /**
|
---|
| 30 | * Returns an exception to be thrown when the consumer attempts to use
|
---|
| 31 | * `<mat-icon>` without including @angular/common/http.
|
---|
| 32 | * @docs-private
|
---|
| 33 | */
|
---|
| 34 | function getMatIconNoHttpProviderError() {
|
---|
| 35 | return Error('Could not find HttpClient provider for use with Angular Material icons. ' +
|
---|
| 36 | 'Please include the HttpClientModule from @angular/common/http in your ' +
|
---|
| 37 | 'app imports.');
|
---|
| 38 | }
|
---|
| 39 | /**
|
---|
| 40 | * Returns an exception to be thrown when a URL couldn't be sanitized.
|
---|
| 41 | * @param url URL that was attempted to be sanitized.
|
---|
| 42 | * @docs-private
|
---|
| 43 | */
|
---|
| 44 | function getMatIconFailedToSanitizeUrlError(url) {
|
---|
| 45 | return Error(`The URL provided to MatIconRegistry was not trusted as a resource URL ` +
|
---|
| 46 | `via Angular's DomSanitizer. Attempted URL was "${url}".`);
|
---|
| 47 | }
|
---|
| 48 | /**
|
---|
| 49 | * Returns an exception to be thrown when a HTML string couldn't be sanitized.
|
---|
| 50 | * @param literal HTML that was attempted to be sanitized.
|
---|
| 51 | * @docs-private
|
---|
| 52 | */
|
---|
| 53 | function getMatIconFailedToSanitizeLiteralError(literal) {
|
---|
| 54 | return Error(`The literal provided to MatIconRegistry was not trusted as safe HTML by ` +
|
---|
| 55 | `Angular's DomSanitizer. Attempted literal was "${literal}".`);
|
---|
| 56 | }
|
---|
| 57 | /**
|
---|
| 58 | * Configuration for an icon, including the URL and possibly the cached SVG element.
|
---|
| 59 | * @docs-private
|
---|
| 60 | */
|
---|
| 61 | class SvgIconConfig {
|
---|
| 62 | constructor(url, svgText, options) {
|
---|
| 63 | this.url = url;
|
---|
| 64 | this.svgText = svgText;
|
---|
| 65 | this.options = options;
|
---|
| 66 | }
|
---|
| 67 | }
|
---|
| 68 | /**
|
---|
| 69 | * Service to register and display icons used by the `<mat-icon>` component.
|
---|
| 70 | * - Registers icon URLs by namespace and name.
|
---|
| 71 | * - Registers icon set URLs by namespace.
|
---|
| 72 | * - Registers aliases for CSS classes, for use with icon fonts.
|
---|
| 73 | * - Loads icons from URLs and extracts individual icons from icon sets.
|
---|
| 74 | */
|
---|
| 75 | class MatIconRegistry {
|
---|
| 76 | constructor(_httpClient, _sanitizer, document, _errorHandler) {
|
---|
| 77 | this._httpClient = _httpClient;
|
---|
| 78 | this._sanitizer = _sanitizer;
|
---|
| 79 | this._errorHandler = _errorHandler;
|
---|
| 80 | /**
|
---|
| 81 | * URLs and cached SVG elements for individual icons. Keys are of the format "[namespace]:[icon]".
|
---|
| 82 | */
|
---|
| 83 | this._svgIconConfigs = new Map();
|
---|
| 84 | /**
|
---|
| 85 | * SvgIconConfig objects and cached SVG elements for icon sets, keyed by namespace.
|
---|
| 86 | * Multiple icon sets can be registered under the same namespace.
|
---|
| 87 | */
|
---|
| 88 | this._iconSetConfigs = new Map();
|
---|
| 89 | /** Cache for icons loaded by direct URLs. */
|
---|
| 90 | this._cachedIconsByUrl = new Map();
|
---|
| 91 | /** In-progress icon fetches. Used to coalesce multiple requests to the same URL. */
|
---|
| 92 | this._inProgressUrlFetches = new Map();
|
---|
| 93 | /** Map from font identifiers to their CSS class names. Used for icon fonts. */
|
---|
| 94 | this._fontCssClassesByAlias = new Map();
|
---|
| 95 | /** Registered icon resolver functions. */
|
---|
| 96 | this._resolvers = [];
|
---|
| 97 | /**
|
---|
| 98 | * The CSS class to apply when an `<mat-icon>` component has no icon name, url, or font specified.
|
---|
| 99 | * The default 'material-icons' value assumes that the material icon font has been loaded as
|
---|
| 100 | * described at http://google.github.io/material-design-icons/#icon-font-for-the-web
|
---|
| 101 | */
|
---|
| 102 | this._defaultFontSetClass = 'material-icons';
|
---|
| 103 | this._document = document;
|
---|
| 104 | }
|
---|
| 105 | /**
|
---|
| 106 | * Registers an icon by URL in the default namespace.
|
---|
| 107 | * @param iconName Name under which the icon should be registered.
|
---|
| 108 | * @param url
|
---|
| 109 | */
|
---|
| 110 | addSvgIcon(iconName, url, options) {
|
---|
| 111 | return this.addSvgIconInNamespace('', iconName, url, options);
|
---|
| 112 | }
|
---|
| 113 | /**
|
---|
| 114 | * Registers an icon using an HTML string in the default namespace.
|
---|
| 115 | * @param iconName Name under which the icon should be registered.
|
---|
| 116 | * @param literal SVG source of the icon.
|
---|
| 117 | */
|
---|
| 118 | addSvgIconLiteral(iconName, literal, options) {
|
---|
| 119 | return this.addSvgIconLiteralInNamespace('', iconName, literal, options);
|
---|
| 120 | }
|
---|
| 121 | /**
|
---|
| 122 | * Registers an icon by URL in the specified namespace.
|
---|
| 123 | * @param namespace Namespace in which the icon should be registered.
|
---|
| 124 | * @param iconName Name under which the icon should be registered.
|
---|
| 125 | * @param url
|
---|
| 126 | */
|
---|
| 127 | addSvgIconInNamespace(namespace, iconName, url, options) {
|
---|
| 128 | return this._addSvgIconConfig(namespace, iconName, new SvgIconConfig(url, null, options));
|
---|
| 129 | }
|
---|
| 130 | /**
|
---|
| 131 | * Registers an icon resolver function with the registry. The function will be invoked with the
|
---|
| 132 | * name and namespace of an icon when the registry tries to resolve the URL from which to fetch
|
---|
| 133 | * the icon. The resolver is expected to return a `SafeResourceUrl` that points to the icon,
|
---|
| 134 | * an object with the icon URL and icon options, or `null` if the icon is not supported. Resolvers
|
---|
| 135 | * will be invoked in the order in which they have been registered.
|
---|
| 136 | * @param resolver Resolver function to be registered.
|
---|
| 137 | */
|
---|
| 138 | addSvgIconResolver(resolver) {
|
---|
| 139 | this._resolvers.push(resolver);
|
---|
| 140 | return this;
|
---|
| 141 | }
|
---|
| 142 | /**
|
---|
| 143 | * Registers an icon using an HTML string in the specified namespace.
|
---|
| 144 | * @param namespace Namespace in which the icon should be registered.
|
---|
| 145 | * @param iconName Name under which the icon should be registered.
|
---|
| 146 | * @param literal SVG source of the icon.
|
---|
| 147 | */
|
---|
| 148 | addSvgIconLiteralInNamespace(namespace, iconName, literal, options) {
|
---|
| 149 | const cleanLiteral = this._sanitizer.sanitize(SecurityContext.HTML, literal);
|
---|
| 150 | // TODO: add an ngDevMode check
|
---|
| 151 | if (!cleanLiteral) {
|
---|
| 152 | throw getMatIconFailedToSanitizeLiteralError(literal);
|
---|
| 153 | }
|
---|
| 154 | return this._addSvgIconConfig(namespace, iconName, new SvgIconConfig('', cleanLiteral, options));
|
---|
| 155 | }
|
---|
| 156 | /**
|
---|
| 157 | * Registers an icon set by URL in the default namespace.
|
---|
| 158 | * @param url
|
---|
| 159 | */
|
---|
| 160 | addSvgIconSet(url, options) {
|
---|
| 161 | return this.addSvgIconSetInNamespace('', url, options);
|
---|
| 162 | }
|
---|
| 163 | /**
|
---|
| 164 | * Registers an icon set using an HTML string in the default namespace.
|
---|
| 165 | * @param literal SVG source of the icon set.
|
---|
| 166 | */
|
---|
| 167 | addSvgIconSetLiteral(literal, options) {
|
---|
| 168 | return this.addSvgIconSetLiteralInNamespace('', literal, options);
|
---|
| 169 | }
|
---|
| 170 | /**
|
---|
| 171 | * Registers an icon set by URL in the specified namespace.
|
---|
| 172 | * @param namespace Namespace in which to register the icon set.
|
---|
| 173 | * @param url
|
---|
| 174 | */
|
---|
| 175 | addSvgIconSetInNamespace(namespace, url, options) {
|
---|
| 176 | return this._addSvgIconSetConfig(namespace, new SvgIconConfig(url, null, options));
|
---|
| 177 | }
|
---|
| 178 | /**
|
---|
| 179 | * Registers an icon set using an HTML string in the specified namespace.
|
---|
| 180 | * @param namespace Namespace in which to register the icon set.
|
---|
| 181 | * @param literal SVG source of the icon set.
|
---|
| 182 | */
|
---|
| 183 | addSvgIconSetLiteralInNamespace(namespace, literal, options) {
|
---|
| 184 | const cleanLiteral = this._sanitizer.sanitize(SecurityContext.HTML, literal);
|
---|
| 185 | if (!cleanLiteral) {
|
---|
| 186 | throw getMatIconFailedToSanitizeLiteralError(literal);
|
---|
| 187 | }
|
---|
| 188 | return this._addSvgIconSetConfig(namespace, new SvgIconConfig('', cleanLiteral, options));
|
---|
| 189 | }
|
---|
| 190 | /**
|
---|
| 191 | * Defines an alias for a CSS class name to be used for icon fonts. Creating an matIcon
|
---|
| 192 | * component with the alias as the fontSet input will cause the class name to be applied
|
---|
| 193 | * to the `<mat-icon>` element.
|
---|
| 194 | *
|
---|
| 195 | * @param alias Alias for the font.
|
---|
| 196 | * @param className Class name override to be used instead of the alias.
|
---|
| 197 | */
|
---|
| 198 | registerFontClassAlias(alias, className = alias) {
|
---|
| 199 | this._fontCssClassesByAlias.set(alias, className);
|
---|
| 200 | return this;
|
---|
| 201 | }
|
---|
| 202 | /**
|
---|
| 203 | * Returns the CSS class name associated with the alias by a previous call to
|
---|
| 204 | * registerFontClassAlias. If no CSS class has been associated, returns the alias unmodified.
|
---|
| 205 | */
|
---|
| 206 | classNameForFontAlias(alias) {
|
---|
| 207 | return this._fontCssClassesByAlias.get(alias) || alias;
|
---|
| 208 | }
|
---|
| 209 | /**
|
---|
| 210 | * Sets the CSS class name to be used for icon fonts when an `<mat-icon>` component does not
|
---|
| 211 | * have a fontSet input value, and is not loading an icon by name or URL.
|
---|
| 212 | *
|
---|
| 213 | * @param className
|
---|
| 214 | */
|
---|
| 215 | setDefaultFontSetClass(className) {
|
---|
| 216 | this._defaultFontSetClass = className;
|
---|
| 217 | return this;
|
---|
| 218 | }
|
---|
| 219 | /**
|
---|
| 220 | * Returns the CSS class name to be used for icon fonts when an `<mat-icon>` component does not
|
---|
| 221 | * have a fontSet input value, and is not loading an icon by name or URL.
|
---|
| 222 | */
|
---|
| 223 | getDefaultFontSetClass() {
|
---|
| 224 | return this._defaultFontSetClass;
|
---|
| 225 | }
|
---|
| 226 | /**
|
---|
| 227 | * Returns an Observable that produces the icon (as an `<svg>` DOM element) from the given URL.
|
---|
| 228 | * The response from the URL may be cached so this will not always cause an HTTP request, but
|
---|
| 229 | * the produced element will always be a new copy of the originally fetched icon. (That is,
|
---|
| 230 | * it will not contain any modifications made to elements previously returned).
|
---|
| 231 | *
|
---|
| 232 | * @param safeUrl URL from which to fetch the SVG icon.
|
---|
| 233 | */
|
---|
| 234 | getSvgIconFromUrl(safeUrl) {
|
---|
| 235 | const url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl);
|
---|
| 236 | if (!url) {
|
---|
| 237 | throw getMatIconFailedToSanitizeUrlError(safeUrl);
|
---|
| 238 | }
|
---|
| 239 | const cachedIcon = this._cachedIconsByUrl.get(url);
|
---|
| 240 | if (cachedIcon) {
|
---|
| 241 | return of(cloneSvg(cachedIcon));
|
---|
| 242 | }
|
---|
| 243 | return this._loadSvgIconFromConfig(new SvgIconConfig(safeUrl, null)).pipe(tap(svg => this._cachedIconsByUrl.set(url, svg)), map(svg => cloneSvg(svg)));
|
---|
| 244 | }
|
---|
| 245 | /**
|
---|
| 246 | * Returns an Observable that produces the icon (as an `<svg>` DOM element) with the given name
|
---|
| 247 | * and namespace. The icon must have been previously registered with addIcon or addIconSet;
|
---|
| 248 | * if not, the Observable will throw an error.
|
---|
| 249 | *
|
---|
| 250 | * @param name Name of the icon to be retrieved.
|
---|
| 251 | * @param namespace Namespace in which to look for the icon.
|
---|
| 252 | */
|
---|
| 253 | getNamedSvgIcon(name, namespace = '') {
|
---|
| 254 | const key = iconKey(namespace, name);
|
---|
| 255 | let config = this._svgIconConfigs.get(key);
|
---|
| 256 | // Return (copy of) cached icon if possible.
|
---|
| 257 | if (config) {
|
---|
| 258 | return this._getSvgFromConfig(config);
|
---|
| 259 | }
|
---|
| 260 | // Otherwise try to resolve the config from one of the resolver functions.
|
---|
| 261 | config = this._getIconConfigFromResolvers(namespace, name);
|
---|
| 262 | if (config) {
|
---|
| 263 | this._svgIconConfigs.set(key, config);
|
---|
| 264 | return this._getSvgFromConfig(config);
|
---|
| 265 | }
|
---|
| 266 | // See if we have any icon sets registered for the namespace.
|
---|
| 267 | const iconSetConfigs = this._iconSetConfigs.get(namespace);
|
---|
| 268 | if (iconSetConfigs) {
|
---|
| 269 | return this._getSvgFromIconSetConfigs(name, iconSetConfigs);
|
---|
| 270 | }
|
---|
| 271 | return throwError(getMatIconNameNotFoundError(key));
|
---|
| 272 | }
|
---|
| 273 | ngOnDestroy() {
|
---|
| 274 | this._resolvers = [];
|
---|
| 275 | this._svgIconConfigs.clear();
|
---|
| 276 | this._iconSetConfigs.clear();
|
---|
| 277 | this._cachedIconsByUrl.clear();
|
---|
| 278 | }
|
---|
| 279 | /**
|
---|
| 280 | * Returns the cached icon for a SvgIconConfig if available, or fetches it from its URL if not.
|
---|
| 281 | */
|
---|
| 282 | _getSvgFromConfig(config) {
|
---|
| 283 | if (config.svgText) {
|
---|
| 284 | // We already have the SVG element for this icon, return a copy.
|
---|
| 285 | return of(cloneSvg(this._svgElementFromConfig(config)));
|
---|
| 286 | }
|
---|
| 287 | else {
|
---|
| 288 | // Fetch the icon from the config's URL, cache it, and return a copy.
|
---|
| 289 | return this._loadSvgIconFromConfig(config).pipe(map(svg => cloneSvg(svg)));
|
---|
| 290 | }
|
---|
| 291 | }
|
---|
| 292 | /**
|
---|
| 293 | * Attempts to find an icon with the specified name in any of the SVG icon sets.
|
---|
| 294 | * First searches the available cached icons for a nested element with a matching name, and
|
---|
| 295 | * if found copies the element to a new `<svg>` element. If not found, fetches all icon sets
|
---|
| 296 | * that have not been cached, and searches again after all fetches are completed.
|
---|
| 297 | * The returned Observable produces the SVG element if possible, and throws
|
---|
| 298 | * an error if no icon with the specified name can be found.
|
---|
| 299 | */
|
---|
| 300 | _getSvgFromIconSetConfigs(name, iconSetConfigs) {
|
---|
| 301 | // For all the icon set SVG elements we've fetched, see if any contain an icon with the
|
---|
| 302 | // requested name.
|
---|
| 303 | const namedIcon = this._extractIconWithNameFromAnySet(name, iconSetConfigs);
|
---|
| 304 | if (namedIcon) {
|
---|
| 305 | // We could cache namedIcon in _svgIconConfigs, but since we have to make a copy every
|
---|
| 306 | // time anyway, there's probably not much advantage compared to just always extracting
|
---|
| 307 | // it from the icon set.
|
---|
| 308 | return of(namedIcon);
|
---|
| 309 | }
|
---|
| 310 | // Not found in any cached icon sets. If there are icon sets with URLs that we haven't
|
---|
| 311 | // fetched, fetch them now and look for iconName in the results.
|
---|
| 312 | const iconSetFetchRequests = iconSetConfigs
|
---|
| 313 | .filter(iconSetConfig => !iconSetConfig.svgText)
|
---|
| 314 | .map(iconSetConfig => {
|
---|
| 315 | return this._loadSvgIconSetFromConfig(iconSetConfig).pipe(catchError((err) => {
|
---|
| 316 | const url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, iconSetConfig.url);
|
---|
| 317 | // Swallow errors fetching individual URLs so the
|
---|
| 318 | // combined Observable won't necessarily fail.
|
---|
| 319 | const errorMessage = `Loading icon set URL: ${url} failed: ${err.message}`;
|
---|
| 320 | this._errorHandler.handleError(new Error(errorMessage));
|
---|
| 321 | return of(null);
|
---|
| 322 | }));
|
---|
| 323 | });
|
---|
| 324 | // Fetch all the icon set URLs. When the requests complete, every IconSet should have a
|
---|
| 325 | // cached SVG element (unless the request failed), and we can check again for the icon.
|
---|
| 326 | return forkJoin(iconSetFetchRequests).pipe(map(() => {
|
---|
| 327 | const foundIcon = this._extractIconWithNameFromAnySet(name, iconSetConfigs);
|
---|
| 328 | // TODO: add an ngDevMode check
|
---|
| 329 | if (!foundIcon) {
|
---|
| 330 | throw getMatIconNameNotFoundError(name);
|
---|
| 331 | }
|
---|
| 332 | return foundIcon;
|
---|
| 333 | }));
|
---|
| 334 | }
|
---|
| 335 | /**
|
---|
| 336 | * Searches the cached SVG elements for the given icon sets for a nested icon element whose "id"
|
---|
| 337 | * tag matches the specified name. If found, copies the nested element to a new SVG element and
|
---|
| 338 | * returns it. Returns null if no matching element is found.
|
---|
| 339 | */
|
---|
| 340 | _extractIconWithNameFromAnySet(iconName, iconSetConfigs) {
|
---|
| 341 | // Iterate backwards, so icon sets added later have precedence.
|
---|
| 342 | for (let i = iconSetConfigs.length - 1; i >= 0; i--) {
|
---|
| 343 | const config = iconSetConfigs[i];
|
---|
| 344 | // Parsing the icon set's text into an SVG element can be expensive. We can avoid some of
|
---|
| 345 | // the parsing by doing a quick check using `indexOf` to see if there's any chance for the
|
---|
| 346 | // icon to be in the set. This won't be 100% accurate, but it should help us avoid at least
|
---|
| 347 | // some of the parsing.
|
---|
| 348 | if (config.svgText && config.svgText.indexOf(iconName) > -1) {
|
---|
| 349 | const svg = this._svgElementFromConfig(config);
|
---|
| 350 | const foundIcon = this._extractSvgIconFromSet(svg, iconName, config.options);
|
---|
| 351 | if (foundIcon) {
|
---|
| 352 | return foundIcon;
|
---|
| 353 | }
|
---|
| 354 | }
|
---|
| 355 | }
|
---|
| 356 | return null;
|
---|
| 357 | }
|
---|
| 358 | /**
|
---|
| 359 | * Loads the content of the icon URL specified in the SvgIconConfig and creates an SVG element
|
---|
| 360 | * from it.
|
---|
| 361 | */
|
---|
| 362 | _loadSvgIconFromConfig(config) {
|
---|
| 363 | return this._fetchIcon(config).pipe(tap(svgText => config.svgText = svgText), map(() => this._svgElementFromConfig(config)));
|
---|
| 364 | }
|
---|
| 365 | /**
|
---|
| 366 | * Loads the content of the icon set URL specified in the
|
---|
| 367 | * SvgIconConfig and attaches it to the config.
|
---|
| 368 | */
|
---|
| 369 | _loadSvgIconSetFromConfig(config) {
|
---|
| 370 | if (config.svgText) {
|
---|
| 371 | return of(null);
|
---|
| 372 | }
|
---|
| 373 | return this._fetchIcon(config).pipe(tap(svgText => config.svgText = svgText));
|
---|
| 374 | }
|
---|
| 375 | /**
|
---|
| 376 | * Searches the cached element of the given SvgIconConfig for a nested icon element whose "id"
|
---|
| 377 | * tag matches the specified name. If found, copies the nested element to a new SVG element and
|
---|
| 378 | * returns it. Returns null if no matching element is found.
|
---|
| 379 | */
|
---|
| 380 | _extractSvgIconFromSet(iconSet, iconName, options) {
|
---|
| 381 | // Use the `id="iconName"` syntax in order to escape special
|
---|
| 382 | // characters in the ID (versus using the #iconName syntax).
|
---|
| 383 | const iconSource = iconSet.querySelector(`[id="${iconName}"]`);
|
---|
| 384 | if (!iconSource) {
|
---|
| 385 | return null;
|
---|
| 386 | }
|
---|
| 387 | // Clone the element and remove the ID to prevent multiple elements from being added
|
---|
| 388 | // to the page with the same ID.
|
---|
| 389 | const iconElement = iconSource.cloneNode(true);
|
---|
| 390 | iconElement.removeAttribute('id');
|
---|
| 391 | // If the icon node is itself an <svg> node, clone and return it directly. If not, set it as
|
---|
| 392 | // the content of a new <svg> node.
|
---|
| 393 | if (iconElement.nodeName.toLowerCase() === 'svg') {
|
---|
| 394 | return this._setSvgAttributes(iconElement, options);
|
---|
| 395 | }
|
---|
| 396 | // If the node is a <symbol>, it won't be rendered so we have to convert it into <svg>. Note
|
---|
| 397 | // that the same could be achieved by referring to it via <use href="#id">, however the <use>
|
---|
| 398 | // tag is problematic on Firefox, because it needs to include the current page path.
|
---|
| 399 | if (iconElement.nodeName.toLowerCase() === 'symbol') {
|
---|
| 400 | return this._setSvgAttributes(this._toSvgElement(iconElement), options);
|
---|
| 401 | }
|
---|
| 402 | // createElement('SVG') doesn't work as expected; the DOM ends up with
|
---|
| 403 | // the correct nodes, but the SVG content doesn't render. Instead we
|
---|
| 404 | // have to create an empty SVG node using innerHTML and append its content.
|
---|
| 405 | // Elements created using DOMParser.parseFromString have the same problem.
|
---|
| 406 | // http://stackoverflow.com/questions/23003278/svg-innerhtml-in-firefox-can-not-display
|
---|
| 407 | const svg = this._svgElementFromString('<svg></svg>');
|
---|
| 408 | // Clone the node so we don't remove it from the parent icon set element.
|
---|
| 409 | svg.appendChild(iconElement);
|
---|
| 410 | return this._setSvgAttributes(svg, options);
|
---|
| 411 | }
|
---|
| 412 | /**
|
---|
| 413 | * Creates a DOM element from the given SVG string.
|
---|
| 414 | */
|
---|
| 415 | _svgElementFromString(str) {
|
---|
| 416 | const div = this._document.createElement('DIV');
|
---|
| 417 | div.innerHTML = str;
|
---|
| 418 | const svg = div.querySelector('svg');
|
---|
| 419 | // TODO: add an ngDevMode check
|
---|
| 420 | if (!svg) {
|
---|
| 421 | throw Error('<svg> tag not found');
|
---|
| 422 | }
|
---|
| 423 | return svg;
|
---|
| 424 | }
|
---|
| 425 | /**
|
---|
| 426 | * Converts an element into an SVG node by cloning all of its children.
|
---|
| 427 | */
|
---|
| 428 | _toSvgElement(element) {
|
---|
| 429 | const svg = this._svgElementFromString('<svg></svg>');
|
---|
| 430 | const attributes = element.attributes;
|
---|
| 431 | // Copy over all the attributes from the `symbol` to the new SVG, except the id.
|
---|
| 432 | for (let i = 0; i < attributes.length; i++) {
|
---|
| 433 | const { name, value } = attributes[i];
|
---|
| 434 | if (name !== 'id') {
|
---|
| 435 | svg.setAttribute(name, value);
|
---|
| 436 | }
|
---|
| 437 | }
|
---|
| 438 | for (let i = 0; i < element.childNodes.length; i++) {
|
---|
| 439 | if (element.childNodes[i].nodeType === this._document.ELEMENT_NODE) {
|
---|
| 440 | svg.appendChild(element.childNodes[i].cloneNode(true));
|
---|
| 441 | }
|
---|
| 442 | }
|
---|
| 443 | return svg;
|
---|
| 444 | }
|
---|
| 445 | /**
|
---|
| 446 | * Sets the default attributes for an SVG element to be used as an icon.
|
---|
| 447 | */
|
---|
| 448 | _setSvgAttributes(svg, options) {
|
---|
| 449 | svg.setAttribute('fit', '');
|
---|
| 450 | svg.setAttribute('height', '100%');
|
---|
| 451 | svg.setAttribute('width', '100%');
|
---|
| 452 | svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
---|
| 453 | svg.setAttribute('focusable', 'false'); // Disable IE11 default behavior to make SVGs focusable.
|
---|
| 454 | if (options && options.viewBox) {
|
---|
| 455 | svg.setAttribute('viewBox', options.viewBox);
|
---|
| 456 | }
|
---|
| 457 | return svg;
|
---|
| 458 | }
|
---|
| 459 | /**
|
---|
| 460 | * Returns an Observable which produces the string contents of the given icon. Results may be
|
---|
| 461 | * cached, so future calls with the same URL may not cause another HTTP request.
|
---|
| 462 | */
|
---|
| 463 | _fetchIcon(iconConfig) {
|
---|
| 464 | var _a;
|
---|
| 465 | const { url: safeUrl, options } = iconConfig;
|
---|
| 466 | const withCredentials = (_a = options === null || options === void 0 ? void 0 : options.withCredentials) !== null && _a !== void 0 ? _a : false;
|
---|
| 467 | if (!this._httpClient) {
|
---|
| 468 | throw getMatIconNoHttpProviderError();
|
---|
| 469 | }
|
---|
| 470 | // TODO: add an ngDevMode check
|
---|
| 471 | if (safeUrl == null) {
|
---|
| 472 | throw Error(`Cannot fetch icon from URL "${safeUrl}".`);
|
---|
| 473 | }
|
---|
| 474 | const url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl);
|
---|
| 475 | // TODO: add an ngDevMode check
|
---|
| 476 | if (!url) {
|
---|
| 477 | throw getMatIconFailedToSanitizeUrlError(safeUrl);
|
---|
| 478 | }
|
---|
| 479 | // Store in-progress fetches to avoid sending a duplicate request for a URL when there is
|
---|
| 480 | // already a request in progress for that URL. It's necessary to call share() on the
|
---|
| 481 | // Observable returned by http.get() so that multiple subscribers don't cause multiple XHRs.
|
---|
| 482 | const inProgressFetch = this._inProgressUrlFetches.get(url);
|
---|
| 483 | if (inProgressFetch) {
|
---|
| 484 | return inProgressFetch;
|
---|
| 485 | }
|
---|
| 486 | const req = this._httpClient.get(url, { responseType: 'text', withCredentials }).pipe(finalize(() => this._inProgressUrlFetches.delete(url)), share());
|
---|
| 487 | this._inProgressUrlFetches.set(url, req);
|
---|
| 488 | return req;
|
---|
| 489 | }
|
---|
| 490 | /**
|
---|
| 491 | * Registers an icon config by name in the specified namespace.
|
---|
| 492 | * @param namespace Namespace in which to register the icon config.
|
---|
| 493 | * @param iconName Name under which to register the config.
|
---|
| 494 | * @param config Config to be registered.
|
---|
| 495 | */
|
---|
| 496 | _addSvgIconConfig(namespace, iconName, config) {
|
---|
| 497 | this._svgIconConfigs.set(iconKey(namespace, iconName), config);
|
---|
| 498 | return this;
|
---|
| 499 | }
|
---|
| 500 | /**
|
---|
| 501 | * Registers an icon set config in the specified namespace.
|
---|
| 502 | * @param namespace Namespace in which to register the icon config.
|
---|
| 503 | * @param config Config to be registered.
|
---|
| 504 | */
|
---|
| 505 | _addSvgIconSetConfig(namespace, config) {
|
---|
| 506 | const configNamespace = this._iconSetConfigs.get(namespace);
|
---|
| 507 | if (configNamespace) {
|
---|
| 508 | configNamespace.push(config);
|
---|
| 509 | }
|
---|
| 510 | else {
|
---|
| 511 | this._iconSetConfigs.set(namespace, [config]);
|
---|
| 512 | }
|
---|
| 513 | return this;
|
---|
| 514 | }
|
---|
| 515 | /** Parses a config's text into an SVG element. */
|
---|
| 516 | _svgElementFromConfig(config) {
|
---|
| 517 | if (!config.svgElement) {
|
---|
| 518 | const svg = this._svgElementFromString(config.svgText);
|
---|
| 519 | this._setSvgAttributes(svg, config.options);
|
---|
| 520 | config.svgElement = svg;
|
---|
| 521 | }
|
---|
| 522 | return config.svgElement;
|
---|
| 523 | }
|
---|
| 524 | /** Tries to create an icon config through the registered resolver functions. */
|
---|
| 525 | _getIconConfigFromResolvers(namespace, name) {
|
---|
| 526 | for (let i = 0; i < this._resolvers.length; i++) {
|
---|
| 527 | const result = this._resolvers[i](name, namespace);
|
---|
| 528 | if (result) {
|
---|
| 529 | return isSafeUrlWithOptions(result) ?
|
---|
| 530 | new SvgIconConfig(result.url, null, result.options) :
|
---|
| 531 | new SvgIconConfig(result, null);
|
---|
| 532 | }
|
---|
| 533 | }
|
---|
| 534 | return undefined;
|
---|
| 535 | }
|
---|
| 536 | }
|
---|
| 537 | MatIconRegistry.ɵprov = i0.ɵɵdefineInjectable({ factory: function MatIconRegistry_Factory() { return new MatIconRegistry(i0.ɵɵinject(i1.HttpClient, 8), i0.ɵɵinject(i2.DomSanitizer), i0.ɵɵinject(i3.DOCUMENT, 8), i0.ɵɵinject(i0.ErrorHandler)); }, token: MatIconRegistry, providedIn: "root" });
|
---|
| 538 | MatIconRegistry.decorators = [
|
---|
| 539 | { type: Injectable, args: [{ providedIn: 'root' },] }
|
---|
| 540 | ];
|
---|
| 541 | MatIconRegistry.ctorParameters = () => [
|
---|
| 542 | { type: HttpClient, decorators: [{ type: Optional }] },
|
---|
| 543 | { type: DomSanitizer },
|
---|
| 544 | { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
|
---|
| 545 | { type: ErrorHandler }
|
---|
| 546 | ];
|
---|
| 547 | /** @docs-private */
|
---|
| 548 | function ICON_REGISTRY_PROVIDER_FACTORY(parentRegistry, httpClient, sanitizer, errorHandler, document) {
|
---|
| 549 | return parentRegistry || new MatIconRegistry(httpClient, sanitizer, document, errorHandler);
|
---|
| 550 | }
|
---|
| 551 | /** @docs-private */
|
---|
| 552 | const ICON_REGISTRY_PROVIDER = {
|
---|
| 553 | // If there is already an MatIconRegistry available, use that. Otherwise, provide a new one.
|
---|
| 554 | provide: MatIconRegistry,
|
---|
| 555 | deps: [
|
---|
| 556 | [new Optional(), new SkipSelf(), MatIconRegistry],
|
---|
| 557 | [new Optional(), HttpClient],
|
---|
| 558 | DomSanitizer,
|
---|
| 559 | ErrorHandler,
|
---|
| 560 | [new Optional(), DOCUMENT],
|
---|
| 561 | ],
|
---|
| 562 | useFactory: ICON_REGISTRY_PROVIDER_FACTORY,
|
---|
| 563 | };
|
---|
| 564 | /** Clones an SVGElement while preserving type information. */
|
---|
| 565 | function cloneSvg(svg) {
|
---|
| 566 | return svg.cloneNode(true);
|
---|
| 567 | }
|
---|
| 568 | /** Returns the cache key to use for an icon namespace and name. */
|
---|
| 569 | function iconKey(namespace, name) {
|
---|
| 570 | return namespace + ':' + name;
|
---|
| 571 | }
|
---|
| 572 | function isSafeUrlWithOptions(value) {
|
---|
| 573 | return !!(value.url && value.options);
|
---|
| 574 | }
|
---|
| 575 |
|
---|
| 576 | /**
|
---|
| 577 | * @license
|
---|
| 578 | * Copyright Google LLC All Rights Reserved.
|
---|
| 579 | *
|
---|
| 580 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 581 | * found in the LICENSE file at https://angular.io/license
|
---|
| 582 | */
|
---|
| 583 | // Boilerplate for applying mixins to MatIcon.
|
---|
| 584 | /** @docs-private */
|
---|
| 585 | const _MatIconBase = mixinColor(class {
|
---|
| 586 | constructor(_elementRef) {
|
---|
| 587 | this._elementRef = _elementRef;
|
---|
| 588 | }
|
---|
| 589 | });
|
---|
| 590 | /**
|
---|
| 591 | * Injection token used to provide the current location to `MatIcon`.
|
---|
| 592 | * Used to handle server-side rendering and to stub out during unit tests.
|
---|
| 593 | * @docs-private
|
---|
| 594 | */
|
---|
| 595 | const MAT_ICON_LOCATION = new InjectionToken('mat-icon-location', {
|
---|
| 596 | providedIn: 'root',
|
---|
| 597 | factory: MAT_ICON_LOCATION_FACTORY
|
---|
| 598 | });
|
---|
| 599 | /** @docs-private */
|
---|
| 600 | function MAT_ICON_LOCATION_FACTORY() {
|
---|
| 601 | const _document = inject(DOCUMENT);
|
---|
| 602 | const _location = _document ? _document.location : null;
|
---|
| 603 | return {
|
---|
| 604 | // Note that this needs to be a function, rather than a property, because Angular
|
---|
| 605 | // will only resolve it once, but we want the current path on each call.
|
---|
| 606 | getPathname: () => _location ? (_location.pathname + _location.search) : ''
|
---|
| 607 | };
|
---|
| 608 | }
|
---|
| 609 | /** SVG attributes that accept a FuncIRI (e.g. `url(<something>)`). */
|
---|
| 610 | const funcIriAttributes = [
|
---|
| 611 | 'clip-path',
|
---|
| 612 | 'color-profile',
|
---|
| 613 | 'src',
|
---|
| 614 | 'cursor',
|
---|
| 615 | 'fill',
|
---|
| 616 | 'filter',
|
---|
| 617 | 'marker',
|
---|
| 618 | 'marker-start',
|
---|
| 619 | 'marker-mid',
|
---|
| 620 | 'marker-end',
|
---|
| 621 | 'mask',
|
---|
| 622 | 'stroke'
|
---|
| 623 | ];
|
---|
| 624 | const ɵ0 = attr => `[${attr}]`;
|
---|
| 625 | /** Selector that can be used to find all elements that are using a `FuncIRI`. */
|
---|
| 626 | const funcIriAttributeSelector = funcIriAttributes.map(ɵ0).join(', ');
|
---|
| 627 | /** Regex that can be used to extract the id out of a FuncIRI. */
|
---|
| 628 | const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/;
|
---|
| 629 | /**
|
---|
| 630 | * Component to display an icon. It can be used in the following ways:
|
---|
| 631 | *
|
---|
| 632 | * - Specify the svgIcon input to load an SVG icon from a URL previously registered with the
|
---|
| 633 | * addSvgIcon, addSvgIconInNamespace, addSvgIconSet, or addSvgIconSetInNamespace methods of
|
---|
| 634 | * MatIconRegistry. If the svgIcon value contains a colon it is assumed to be in the format
|
---|
| 635 | * "[namespace]:[name]", if not the value will be the name of an icon in the default namespace.
|
---|
| 636 | * Examples:
|
---|
| 637 | * `<mat-icon svgIcon="left-arrow"></mat-icon>
|
---|
| 638 | * <mat-icon svgIcon="animals:cat"></mat-icon>`
|
---|
| 639 | *
|
---|
| 640 | * - Use a font ligature as an icon by putting the ligature text in the content of the `<mat-icon>`
|
---|
| 641 | * component. By default the Material icons font is used as described at
|
---|
| 642 | * http://google.github.io/material-design-icons/#icon-font-for-the-web. You can specify an
|
---|
| 643 | * alternate font by setting the fontSet input to either the CSS class to apply to use the
|
---|
| 644 | * desired font, or to an alias previously registered with MatIconRegistry.registerFontClassAlias.
|
---|
| 645 | * Examples:
|
---|
| 646 | * `<mat-icon>home</mat-icon>
|
---|
| 647 | * <mat-icon fontSet="myfont">sun</mat-icon>`
|
---|
| 648 | *
|
---|
| 649 | * - Specify a font glyph to be included via CSS rules by setting the fontSet input to specify the
|
---|
| 650 | * font, and the fontIcon input to specify the icon. Typically the fontIcon will specify a
|
---|
| 651 | * CSS class which causes the glyph to be displayed via a :before selector, as in
|
---|
| 652 | * https://fortawesome.github.io/Font-Awesome/examples/
|
---|
| 653 | * Example:
|
---|
| 654 | * `<mat-icon fontSet="fa" fontIcon="alarm"></mat-icon>`
|
---|
| 655 | */
|
---|
| 656 | class MatIcon extends _MatIconBase {
|
---|
| 657 | constructor(elementRef, _iconRegistry, ariaHidden, _location, _errorHandler) {
|
---|
| 658 | super(elementRef);
|
---|
| 659 | this._iconRegistry = _iconRegistry;
|
---|
| 660 | this._location = _location;
|
---|
| 661 | this._errorHandler = _errorHandler;
|
---|
| 662 | this._inline = false;
|
---|
| 663 | /** Subscription to the current in-progress SVG icon request. */
|
---|
| 664 | this._currentIconFetch = Subscription.EMPTY;
|
---|
| 665 | // If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is
|
---|
| 666 | // the right thing to do for the majority of icon use-cases.
|
---|
| 667 | if (!ariaHidden) {
|
---|
| 668 | elementRef.nativeElement.setAttribute('aria-hidden', 'true');
|
---|
| 669 | }
|
---|
| 670 | }
|
---|
| 671 | /**
|
---|
| 672 | * Whether the icon should be inlined, automatically sizing the icon to match the font size of
|
---|
| 673 | * the element the icon is contained in.
|
---|
| 674 | */
|
---|
| 675 | get inline() {
|
---|
| 676 | return this._inline;
|
---|
| 677 | }
|
---|
| 678 | set inline(inline) {
|
---|
| 679 | this._inline = coerceBooleanProperty(inline);
|
---|
| 680 | }
|
---|
| 681 | /** Name of the icon in the SVG icon set. */
|
---|
| 682 | get svgIcon() { return this._svgIcon; }
|
---|
| 683 | set svgIcon(value) {
|
---|
| 684 | if (value !== this._svgIcon) {
|
---|
| 685 | if (value) {
|
---|
| 686 | this._updateSvgIcon(value);
|
---|
| 687 | }
|
---|
| 688 | else if (this._svgIcon) {
|
---|
| 689 | this._clearSvgElement();
|
---|
| 690 | }
|
---|
| 691 | this._svgIcon = value;
|
---|
| 692 | }
|
---|
| 693 | }
|
---|
| 694 | /** Font set that the icon is a part of. */
|
---|
| 695 | get fontSet() { return this._fontSet; }
|
---|
| 696 | set fontSet(value) {
|
---|
| 697 | const newValue = this._cleanupFontValue(value);
|
---|
| 698 | if (newValue !== this._fontSet) {
|
---|
| 699 | this._fontSet = newValue;
|
---|
| 700 | this._updateFontIconClasses();
|
---|
| 701 | }
|
---|
| 702 | }
|
---|
| 703 | /** Name of an icon within a font set. */
|
---|
| 704 | get fontIcon() { return this._fontIcon; }
|
---|
| 705 | set fontIcon(value) {
|
---|
| 706 | const newValue = this._cleanupFontValue(value);
|
---|
| 707 | if (newValue !== this._fontIcon) {
|
---|
| 708 | this._fontIcon = newValue;
|
---|
| 709 | this._updateFontIconClasses();
|
---|
| 710 | }
|
---|
| 711 | }
|
---|
| 712 | /**
|
---|
| 713 | * Splits an svgIcon binding value into its icon set and icon name components.
|
---|
| 714 | * Returns a 2-element array of [(icon set), (icon name)].
|
---|
| 715 | * The separator for the two fields is ':'. If there is no separator, an empty
|
---|
| 716 | * string is returned for the icon set and the entire value is returned for
|
---|
| 717 | * the icon name. If the argument is falsy, returns an array of two empty strings.
|
---|
| 718 | * Throws an error if the name contains two or more ':' separators.
|
---|
| 719 | * Examples:
|
---|
| 720 | * `'social:cake' -> ['social', 'cake']
|
---|
| 721 | * 'penguin' -> ['', 'penguin']
|
---|
| 722 | * null -> ['', '']
|
---|
| 723 | * 'a:b:c' -> (throws Error)`
|
---|
| 724 | */
|
---|
| 725 | _splitIconName(iconName) {
|
---|
| 726 | if (!iconName) {
|
---|
| 727 | return ['', ''];
|
---|
| 728 | }
|
---|
| 729 | const parts = iconName.split(':');
|
---|
| 730 | switch (parts.length) {
|
---|
| 731 | case 1: return ['', parts[0]]; // Use default namespace.
|
---|
| 732 | case 2: return parts;
|
---|
| 733 | default: throw Error(`Invalid icon name: "${iconName}"`); // TODO: add an ngDevMode check
|
---|
| 734 | }
|
---|
| 735 | }
|
---|
| 736 | ngOnInit() {
|
---|
| 737 | // Update font classes because ngOnChanges won't be called if none of the inputs are present,
|
---|
| 738 | // e.g. <mat-icon>arrow</mat-icon> In this case we need to add a CSS class for the default font.
|
---|
| 739 | this._updateFontIconClasses();
|
---|
| 740 | }
|
---|
| 741 | ngAfterViewChecked() {
|
---|
| 742 | const cachedElements = this._elementsWithExternalReferences;
|
---|
| 743 | if (cachedElements && cachedElements.size) {
|
---|
| 744 | const newPath = this._location.getPathname();
|
---|
| 745 | // We need to check whether the URL has changed on each change detection since
|
---|
| 746 | // the browser doesn't have an API that will let us react on link clicks and
|
---|
| 747 | // we can't depend on the Angular router. The references need to be updated,
|
---|
| 748 | // because while most browsers don't care whether the URL is correct after
|
---|
| 749 | // the first render, Safari will break if the user navigates to a different
|
---|
| 750 | // page and the SVG isn't re-rendered.
|
---|
| 751 | if (newPath !== this._previousPath) {
|
---|
| 752 | this._previousPath = newPath;
|
---|
| 753 | this._prependPathToReferences(newPath);
|
---|
| 754 | }
|
---|
| 755 | }
|
---|
| 756 | }
|
---|
| 757 | ngOnDestroy() {
|
---|
| 758 | this._currentIconFetch.unsubscribe();
|
---|
| 759 | if (this._elementsWithExternalReferences) {
|
---|
| 760 | this._elementsWithExternalReferences.clear();
|
---|
| 761 | }
|
---|
| 762 | }
|
---|
| 763 | _usingFontIcon() {
|
---|
| 764 | return !this.svgIcon;
|
---|
| 765 | }
|
---|
| 766 | _setSvgElement(svg) {
|
---|
| 767 | this._clearSvgElement();
|
---|
| 768 | // Workaround for IE11 and Edge ignoring `style` tags inside dynamically-created SVGs.
|
---|
| 769 | // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10898469/
|
---|
| 770 | // Do this before inserting the element into the DOM, in order to avoid a style recalculation.
|
---|
| 771 | const styleTags = svg.querySelectorAll('style');
|
---|
| 772 | for (let i = 0; i < styleTags.length; i++) {
|
---|
| 773 | styleTags[i].textContent += ' ';
|
---|
| 774 | }
|
---|
| 775 | // Note: we do this fix here, rather than the icon registry, because the
|
---|
| 776 | // references have to point to the URL at the time that the icon was created.
|
---|
| 777 | const path = this._location.getPathname();
|
---|
| 778 | this._previousPath = path;
|
---|
| 779 | this._cacheChildrenWithExternalReferences(svg);
|
---|
| 780 | this._prependPathToReferences(path);
|
---|
| 781 | this._elementRef.nativeElement.appendChild(svg);
|
---|
| 782 | }
|
---|
| 783 | _clearSvgElement() {
|
---|
| 784 | const layoutElement = this._elementRef.nativeElement;
|
---|
| 785 | let childCount = layoutElement.childNodes.length;
|
---|
| 786 | if (this._elementsWithExternalReferences) {
|
---|
| 787 | this._elementsWithExternalReferences.clear();
|
---|
| 788 | }
|
---|
| 789 | // Remove existing non-element child nodes and SVGs, and add the new SVG element. Note that
|
---|
| 790 | // we can't use innerHTML, because IE will throw if the element has a data binding.
|
---|
| 791 | while (childCount--) {
|
---|
| 792 | const child = layoutElement.childNodes[childCount];
|
---|
| 793 | // 1 corresponds to Node.ELEMENT_NODE. We remove all non-element nodes in order to get rid
|
---|
| 794 | // of any loose text nodes, as well as any SVG elements in order to remove any old icons.
|
---|
| 795 | if (child.nodeType !== 1 || child.nodeName.toLowerCase() === 'svg') {
|
---|
| 796 | layoutElement.removeChild(child);
|
---|
| 797 | }
|
---|
| 798 | }
|
---|
| 799 | }
|
---|
| 800 | _updateFontIconClasses() {
|
---|
| 801 | if (!this._usingFontIcon()) {
|
---|
| 802 | return;
|
---|
| 803 | }
|
---|
| 804 | const elem = this._elementRef.nativeElement;
|
---|
| 805 | const fontSetClass = this.fontSet ?
|
---|
| 806 | this._iconRegistry.classNameForFontAlias(this.fontSet) :
|
---|
| 807 | this._iconRegistry.getDefaultFontSetClass();
|
---|
| 808 | if (fontSetClass != this._previousFontSetClass) {
|
---|
| 809 | if (this._previousFontSetClass) {
|
---|
| 810 | elem.classList.remove(this._previousFontSetClass);
|
---|
| 811 | }
|
---|
| 812 | if (fontSetClass) {
|
---|
| 813 | elem.classList.add(fontSetClass);
|
---|
| 814 | }
|
---|
| 815 | this._previousFontSetClass = fontSetClass;
|
---|
| 816 | }
|
---|
| 817 | if (this.fontIcon != this._previousFontIconClass) {
|
---|
| 818 | if (this._previousFontIconClass) {
|
---|
| 819 | elem.classList.remove(this._previousFontIconClass);
|
---|
| 820 | }
|
---|
| 821 | if (this.fontIcon) {
|
---|
| 822 | elem.classList.add(this.fontIcon);
|
---|
| 823 | }
|
---|
| 824 | this._previousFontIconClass = this.fontIcon;
|
---|
| 825 | }
|
---|
| 826 | }
|
---|
| 827 | /**
|
---|
| 828 | * Cleans up a value to be used as a fontIcon or fontSet.
|
---|
| 829 | * Since the value ends up being assigned as a CSS class, we
|
---|
| 830 | * have to trim the value and omit space-separated values.
|
---|
| 831 | */
|
---|
| 832 | _cleanupFontValue(value) {
|
---|
| 833 | return typeof value === 'string' ? value.trim().split(' ')[0] : value;
|
---|
| 834 | }
|
---|
| 835 | /**
|
---|
| 836 | * Prepends the current path to all elements that have an attribute pointing to a `FuncIRI`
|
---|
| 837 | * reference. This is required because WebKit browsers require references to be prefixed with
|
---|
| 838 | * the current path, if the page has a `base` tag.
|
---|
| 839 | */
|
---|
| 840 | _prependPathToReferences(path) {
|
---|
| 841 | const elements = this._elementsWithExternalReferences;
|
---|
| 842 | if (elements) {
|
---|
| 843 | elements.forEach((attrs, element) => {
|
---|
| 844 | attrs.forEach(attr => {
|
---|
| 845 | element.setAttribute(attr.name, `url('${path}#${attr.value}')`);
|
---|
| 846 | });
|
---|
| 847 | });
|
---|
| 848 | }
|
---|
| 849 | }
|
---|
| 850 | /**
|
---|
| 851 | * Caches the children of an SVG element that have `url()`
|
---|
| 852 | * references that we need to prefix with the current path.
|
---|
| 853 | */
|
---|
| 854 | _cacheChildrenWithExternalReferences(element) {
|
---|
| 855 | const elementsWithFuncIri = element.querySelectorAll(funcIriAttributeSelector);
|
---|
| 856 | const elements = this._elementsWithExternalReferences =
|
---|
| 857 | this._elementsWithExternalReferences || new Map();
|
---|
| 858 | for (let i = 0; i < elementsWithFuncIri.length; i++) {
|
---|
| 859 | funcIriAttributes.forEach(attr => {
|
---|
| 860 | const elementWithReference = elementsWithFuncIri[i];
|
---|
| 861 | const value = elementWithReference.getAttribute(attr);
|
---|
| 862 | const match = value ? value.match(funcIriPattern) : null;
|
---|
| 863 | if (match) {
|
---|
| 864 | let attributes = elements.get(elementWithReference);
|
---|
| 865 | if (!attributes) {
|
---|
| 866 | attributes = [];
|
---|
| 867 | elements.set(elementWithReference, attributes);
|
---|
| 868 | }
|
---|
| 869 | attributes.push({ name: attr, value: match[1] });
|
---|
| 870 | }
|
---|
| 871 | });
|
---|
| 872 | }
|
---|
| 873 | }
|
---|
| 874 | /** Sets a new SVG icon with a particular name. */
|
---|
| 875 | _updateSvgIcon(rawName) {
|
---|
| 876 | this._svgNamespace = null;
|
---|
| 877 | this._svgName = null;
|
---|
| 878 | this._currentIconFetch.unsubscribe();
|
---|
| 879 | if (rawName) {
|
---|
| 880 | const [namespace, iconName] = this._splitIconName(rawName);
|
---|
| 881 | if (namespace) {
|
---|
| 882 | this._svgNamespace = namespace;
|
---|
| 883 | }
|
---|
| 884 | if (iconName) {
|
---|
| 885 | this._svgName = iconName;
|
---|
| 886 | }
|
---|
| 887 | this._currentIconFetch = this._iconRegistry.getNamedSvgIcon(iconName, namespace)
|
---|
| 888 | .pipe(take(1))
|
---|
| 889 | .subscribe(svg => this._setSvgElement(svg), (err) => {
|
---|
| 890 | const errorMessage = `Error retrieving icon ${namespace}:${iconName}! ${err.message}`;
|
---|
| 891 | this._errorHandler.handleError(new Error(errorMessage));
|
---|
| 892 | });
|
---|
| 893 | }
|
---|
| 894 | }
|
---|
| 895 | }
|
---|
| 896 | MatIcon.decorators = [
|
---|
| 897 | { type: Component, args: [{
|
---|
| 898 | template: '<ng-content></ng-content>',
|
---|
| 899 | selector: 'mat-icon',
|
---|
| 900 | exportAs: 'matIcon',
|
---|
| 901 | inputs: ['color'],
|
---|
| 902 | host: {
|
---|
| 903 | 'role': 'img',
|
---|
| 904 | 'class': 'mat-icon notranslate',
|
---|
| 905 | '[attr.data-mat-icon-type]': '_usingFontIcon() ? "font" : "svg"',
|
---|
| 906 | '[attr.data-mat-icon-name]': '_svgName || fontIcon',
|
---|
| 907 | '[attr.data-mat-icon-namespace]': '_svgNamespace || fontSet',
|
---|
| 908 | '[class.mat-icon-inline]': 'inline',
|
---|
| 909 | '[class.mat-icon-no-color]': 'color !== "primary" && color !== "accent" && color !== "warn"',
|
---|
| 910 | },
|
---|
| 911 | encapsulation: ViewEncapsulation.None,
|
---|
| 912 | changeDetection: ChangeDetectionStrategy.OnPush,
|
---|
[e29cc2e] | 913 | styles: [".mat-icon{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-repeat:no-repeat;display:inline-block;fill:currentColor;height:24px;width:24px}.mat-icon.mat-icon-inline{font-size:inherit;height:inherit;line-height:inherit;width:inherit}[dir=rtl] .mat-icon-rtl-mirror{transform:scale(-1, 1)}.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-icon,.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-icon{display:block}.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-icon-button .mat-icon,.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-icon-button .mat-icon{margin:auto}\n"]
|
---|
[6a3a178] | 914 | },] }
|
---|
| 915 | ];
|
---|
| 916 | MatIcon.ctorParameters = () => [
|
---|
| 917 | { type: ElementRef },
|
---|
| 918 | { type: MatIconRegistry },
|
---|
| 919 | { type: String, decorators: [{ type: Attribute, args: ['aria-hidden',] }] },
|
---|
| 920 | { type: undefined, decorators: [{ type: Inject, args: [MAT_ICON_LOCATION,] }] },
|
---|
| 921 | { type: ErrorHandler }
|
---|
| 922 | ];
|
---|
| 923 | MatIcon.propDecorators = {
|
---|
| 924 | inline: [{ type: Input }],
|
---|
| 925 | svgIcon: [{ type: Input }],
|
---|
| 926 | fontSet: [{ type: Input }],
|
---|
| 927 | fontIcon: [{ type: Input }]
|
---|
| 928 | };
|
---|
| 929 |
|
---|
| 930 | /**
|
---|
| 931 | * @license
|
---|
| 932 | * Copyright Google LLC All Rights Reserved.
|
---|
| 933 | *
|
---|
| 934 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 935 | * found in the LICENSE file at https://angular.io/license
|
---|
| 936 | */
|
---|
| 937 | class MatIconModule {
|
---|
| 938 | }
|
---|
| 939 | MatIconModule.decorators = [
|
---|
| 940 | { type: NgModule, args: [{
|
---|
| 941 | imports: [MatCommonModule],
|
---|
| 942 | exports: [MatIcon, MatCommonModule],
|
---|
| 943 | declarations: [MatIcon],
|
---|
| 944 | },] }
|
---|
| 945 | ];
|
---|
| 946 |
|
---|
| 947 | /**
|
---|
| 948 | * @license
|
---|
| 949 | * Copyright Google LLC All Rights Reserved.
|
---|
| 950 | *
|
---|
| 951 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 952 | * found in the LICENSE file at https://angular.io/license
|
---|
| 953 | */
|
---|
| 954 |
|
---|
| 955 | /**
|
---|
| 956 | * Generated bundle index. Do not edit.
|
---|
| 957 | */
|
---|
| 958 |
|
---|
| 959 | export { ICON_REGISTRY_PROVIDER, ICON_REGISTRY_PROVIDER_FACTORY, MAT_ICON_LOCATION, MAT_ICON_LOCATION_FACTORY, MatIcon, MatIconModule, MatIconRegistry, getMatIconFailedToSanitizeLiteralError, getMatIconFailedToSanitizeUrlError, getMatIconNameNotFoundError, getMatIconNoHttpProviderError, ɵ0 };
|
---|
| 960 | //# sourceMappingURL=icon.js.map
|
---|