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