UNPKG

@angular/material

Version:
401 lines (397 loc) 20.8 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, ElementRef, ErrorHandler, HostAttributeToken, booleanAttribute, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, NgModule } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import { M as MatIconRegistry } from './icon-registry-B2IMBfNA.mjs'; export { d as ICON_REGISTRY_PROVIDER, I as ICON_REGISTRY_PROVIDER_FACTORY, c as getMatIconFailedToSanitizeLiteralError, b as getMatIconFailedToSanitizeUrlError, g as getMatIconNameNotFoundError, a as getMatIconNoHttpProviderError } from './icon-registry-B2IMBfNA.mjs'; import { M as MatCommonModule } from './common-module-WayjW0Pb.mjs'; import '@angular/common/http'; import '@angular/platform-browser'; import '@angular/cdk/a11y'; import '@angular/cdk/bidi'; /** Injection token to be used to override the default options for `mat-icon`. */ const MAT_ICON_DEFAULT_OPTIONS = new InjectionToken('MAT_ICON_DEFAULT_OPTIONS'); /** * Injection token used to provide the current location to `MatIcon`. * Used to handle server-side rendering and to stub out during unit tests. * @docs-private */ const MAT_ICON_LOCATION = new InjectionToken('mat-icon-location', { providedIn: 'root', factory: MAT_ICON_LOCATION_FACTORY, }); /** * @docs-private * @deprecated No longer used, will be removed. * @breaking-change 21.0.0 */ function MAT_ICON_LOCATION_FACTORY() { const _document = inject(DOCUMENT); const _location = _document ? _document.location : null; return { // Note that this needs to be a function, rather than a property, because Angular // will only resolve it once, but we want the current path on each call. getPathname: () => (_location ? _location.pathname + _location.search : ''), }; } /** SVG attributes that accept a FuncIRI (e.g. `url(<something>)`). */ const funcIriAttributes = [ 'clip-path', 'color-profile', 'src', 'cursor', 'fill', 'filter', 'marker', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'stroke', ]; /** Selector that can be used to find all elements that are using a `FuncIRI`. */ const funcIriAttributeSelector = funcIriAttributes.map(attr => `[${attr}]`).join(', '); /** Regex that can be used to extract the id out of a FuncIRI. */ const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/; /** * Component to display an icon. It can be used in the following ways: * * - Specify the svgIcon input to load an SVG icon from a URL previously registered with the * addSvgIcon, addSvgIconInNamespace, addSvgIconSet, or addSvgIconSetInNamespace methods of * MatIconRegistry. If the svgIcon value contains a colon it is assumed to be in the format * "[namespace]:[name]", if not the value will be the name of an icon in the default namespace. * Examples: * `<mat-icon svgIcon="left-arrow"></mat-icon> * <mat-icon svgIcon="animals:cat"></mat-icon>` * * - Use a font ligature as an icon by putting the ligature text in the `fontIcon` attribute or the * content of the `<mat-icon>` component. If you register a custom font class, don't forget to also * include the special class `mat-ligature-font`. It is recommended to use the attribute alternative * to prevent the ligature text to be selectable and to appear in search engine results. * By default, the Material icons font is used as described at * http://google.github.io/material-design-icons/#icon-font-for-the-web. You can specify an * alternate font by setting the fontSet input to either the CSS class to apply to use the * desired font, or to an alias previously registered with MatIconRegistry.registerFontClassAlias. * Examples: * `<mat-icon fontIcon="home"></mat-icon> * <mat-icon>home</mat-icon> * <mat-icon fontSet="myfont" fontIcon="sun"></mat-icon> * <mat-icon fontSet="myfont">sun</mat-icon>` * * - Specify a font glyph to be included via CSS rules by setting the fontSet input to specify the * font, and the fontIcon input to specify the icon. Typically the fontIcon will specify a * CSS class which causes the glyph to be displayed via a :before selector, as in * https://fontawesome-v4.github.io/examples/ * Example: * `<mat-icon fontSet="fa" fontIcon="alarm"></mat-icon>` */ class MatIcon { _elementRef = inject(ElementRef); _iconRegistry = inject(MatIconRegistry); _location = inject(MAT_ICON_LOCATION); _errorHandler = inject(ErrorHandler); _defaultColor; /** * Theme color of the icon. This API is supported in M2 themes only, it * has no effect in M3 themes. For color customization in M3, see https://material.angular.io/components/icon/styling. * * For information on applying color variants in M3, see * https://material.angular.io/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants */ get color() { return this._color || this._defaultColor; } set color(value) { this._color = value; } _color; /** * Whether the icon should be inlined, automatically sizing the icon to match the font size of * the element the icon is contained in. */ inline = false; /** Name of the icon in the SVG icon set. */ get svgIcon() { return this._svgIcon; } set svgIcon(value) { if (value !== this._svgIcon) { if (value) { this._updateSvgIcon(value); } else if (this._svgIcon) { this._clearSvgElement(); } this._svgIcon = value; } } _svgIcon; /** Font set that the icon is a part of. */ get fontSet() { return this._fontSet; } set fontSet(value) { const newValue = this._cleanupFontValue(value); if (newValue !== this._fontSet) { this._fontSet = newValue; this._updateFontIconClasses(); } } _fontSet; /** Name of an icon within a font set. */ get fontIcon() { return this._fontIcon; } set fontIcon(value) { const newValue = this._cleanupFontValue(value); if (newValue !== this._fontIcon) { this._fontIcon = newValue; this._updateFontIconClasses(); } } _fontIcon; _previousFontSetClass = []; _previousFontIconClass; _svgName; _svgNamespace; /** Keeps track of the current page path. */ _previousPath; /** Keeps track of the elements and attributes that we've prefixed with the current path. */ _elementsWithExternalReferences; /** Subscription to the current in-progress SVG icon request. */ _currentIconFetch = Subscription.EMPTY; constructor() { const ariaHidden = inject(new HostAttributeToken('aria-hidden'), { optional: true }); const defaults = inject(MAT_ICON_DEFAULT_OPTIONS, { optional: true }); if (defaults) { if (defaults.color) { this.color = this._defaultColor = defaults.color; } if (defaults.fontSet) { this.fontSet = defaults.fontSet; } } // If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is // the right thing to do for the majority of icon use-cases. if (!ariaHidden) { this._elementRef.nativeElement.setAttribute('aria-hidden', 'true'); } } /** * Splits an svgIcon binding value into its icon set and icon name components. * Returns a 2-element array of [(icon set), (icon name)]. * The separator for the two fields is ':'. If there is no separator, an empty * string is returned for the icon set and the entire value is returned for * the icon name. If the argument is falsy, returns an array of two empty strings. * Throws an error if the name contains two or more ':' separators. * Examples: * `'social:cake' -> ['social', 'cake'] * 'penguin' -> ['', 'penguin'] * null -> ['', ''] * 'a:b:c' -> (throws Error)` */ _splitIconName(iconName) { if (!iconName) { return ['', '']; } const parts = iconName.split(':'); switch (parts.length) { case 1: return ['', parts[0]]; // Use default namespace. case 2: return parts; default: throw Error(`Invalid icon name: "${iconName}"`); // TODO: add an ngDevMode check } } ngOnInit() { // Update font classes because ngOnChanges won't be called if none of the inputs are present, // e.g. <mat-icon>arrow</mat-icon> In this case we need to add a CSS class for the default font. this._updateFontIconClasses(); } ngAfterViewChecked() { const cachedElements = this._elementsWithExternalReferences; if (cachedElements && cachedElements.size) { const newPath = this._location.getPathname(); // We need to check whether the URL has changed on each change detection since // the browser doesn't have an API that will let us react on link clicks and // we can't depend on the Angular router. The references need to be updated, // because while most browsers don't care whether the URL is correct after // the first render, Safari will break if the user navigates to a different // page and the SVG isn't re-rendered. if (newPath !== this._previousPath) { this._previousPath = newPath; this._prependPathToReferences(newPath); } } } ngOnDestroy() { this._currentIconFetch.unsubscribe(); if (this._elementsWithExternalReferences) { this._elementsWithExternalReferences.clear(); } } _usingFontIcon() { return !this.svgIcon; } _setSvgElement(svg) { this._clearSvgElement(); // Note: we do this fix here, rather than the icon registry, because the // references have to point to the URL at the time that the icon was created. const path = this._location.getPathname(); this._previousPath = path; this._cacheChildrenWithExternalReferences(svg); this._prependPathToReferences(path); this._elementRef.nativeElement.appendChild(svg); } _clearSvgElement() { const layoutElement = this._elementRef.nativeElement; let childCount = layoutElement.childNodes.length; if (this._elementsWithExternalReferences) { this._elementsWithExternalReferences.clear(); } // Remove existing non-element child nodes and SVGs, and add the new SVG element. Note that // we can't use innerHTML, because IE will throw if the element has a data binding. while (childCount--) { const child = layoutElement.childNodes[childCount]; // 1 corresponds to Node.ELEMENT_NODE. We remove all non-element nodes in order to get rid // of any loose text nodes, as well as any SVG elements in order to remove any old icons. if (child.nodeType !== 1 || child.nodeName.toLowerCase() === 'svg') { child.remove(); } } } _updateFontIconClasses() { if (!this._usingFontIcon()) { return; } const elem = this._elementRef.nativeElement; const fontSetClasses = (this.fontSet ? this._iconRegistry.classNameForFontAlias(this.fontSet).split(/ +/) : this._iconRegistry.getDefaultFontSetClass()).filter(className => className.length > 0); this._previousFontSetClass.forEach(className => elem.classList.remove(className)); fontSetClasses.forEach(className => elem.classList.add(className)); this._previousFontSetClass = fontSetClasses; if (this.fontIcon !== this._previousFontIconClass && !fontSetClasses.includes('mat-ligature-font')) { if (this._previousFontIconClass) { elem.classList.remove(this._previousFontIconClass); } if (this.fontIcon) { elem.classList.add(this.fontIcon); } this._previousFontIconClass = this.fontIcon; } } /** * Cleans up a value to be used as a fontIcon or fontSet. * Since the value ends up being assigned as a CSS class, we * have to trim the value and omit space-separated values. */ _cleanupFontValue(value) { return typeof value === 'string' ? value.trim().split(' ')[0] : value; } /** * Prepends the current path to all elements that have an attribute pointing to a `FuncIRI` * reference. This is required because WebKit browsers require references to be prefixed with * the current path, if the page has a `base` tag. */ _prependPathToReferences(path) { const elements = this._elementsWithExternalReferences; if (elements) { elements.forEach((attrs, element) => { attrs.forEach(attr => { element.setAttribute(attr.name, `url('${path}#${attr.value}')`); }); }); } } /** * Caches the children of an SVG element that have `url()` * references that we need to prefix with the current path. */ _cacheChildrenWithExternalReferences(element) { const elementsWithFuncIri = element.querySelectorAll(funcIriAttributeSelector); const elements = (this._elementsWithExternalReferences = this._elementsWithExternalReferences || new Map()); for (let i = 0; i < elementsWithFuncIri.length; i++) { funcIriAttributes.forEach(attr => { const elementWithReference = elementsWithFuncIri[i]; const value = elementWithReference.getAttribute(attr); const match = value ? value.match(funcIriPattern) : null; if (match) { let attributes = elements.get(elementWithReference); if (!attributes) { attributes = []; elements.set(elementWithReference, attributes); } attributes.push({ name: attr, value: match[1] }); } }); } } /** Sets a new SVG icon with a particular name. */ _updateSvgIcon(rawName) { this._svgNamespace = null; this._svgName = null; this._currentIconFetch.unsubscribe(); if (rawName) { const [namespace, iconName] = this._splitIconName(rawName); if (namespace) { this._svgNamespace = namespace; } if (iconName) { this._svgName = iconName; } this._currentIconFetch = this._iconRegistry .getNamedSvgIcon(iconName, namespace) .pipe(take(1)) .subscribe(svg => this._setSvgElement(svg), (err) => { const errorMessage = `Error retrieving icon ${namespace}:${iconName}! ${err.message}`; this._errorHandler.handleError(new Error(errorMessage)); }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIcon, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.2.6", type: MatIcon, isStandalone: true, selector: "mat-icon", inputs: { color: "color", inline: ["inline", "inline", booleanAttribute], svgIcon: "svgIcon", fontSet: "fontSet", fontIcon: "fontIcon" }, host: { attributes: { "role": "img" }, properties: { "class": "color ? \"mat-\" + color : \"\"", "attr.data-mat-icon-type": "_usingFontIcon() ? \"font\" : \"svg\"", "attr.data-mat-icon-name": "_svgName || fontIcon", "attr.data-mat-icon-namespace": "_svgNamespace || fontSet", "attr.fontIcon": "_usingFontIcon() ? fontIcon : null", "class.mat-icon-inline": "inline", "class.mat-icon-no-color": "color !== \"primary\" && color !== \"accent\" && color !== \"warn\"" }, classAttribute: "mat-icon notranslate" }, exportAs: ["matIcon"], ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["mat-icon,mat-icon.mat-primary,mat-icon.mat-accent,mat-icon.mat-warn{color:var(--mat-icon-color, inherit)}.mat-icon{-webkit-user-select:none;user-select:none;background-repeat:no-repeat;display:inline-block;fill:currentColor;height:24px;width:24px;overflow:hidden}.mat-icon.mat-icon-inline{font-size:inherit;height:inherit;line-height:inherit;width:inherit}.mat-icon.mat-ligature-font[fontIcon]::before{content:attr(fontIcon)}[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"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIcon, decorators: [{ type: Component, args: [{ template: '<ng-content></ng-content>', selector: 'mat-icon', exportAs: 'matIcon', host: { 'role': 'img', 'class': 'mat-icon notranslate', '[class]': 'color ? "mat-" + color : ""', '[attr.data-mat-icon-type]': '_usingFontIcon() ? "font" : "svg"', '[attr.data-mat-icon-name]': '_svgName || fontIcon', '[attr.data-mat-icon-namespace]': '_svgNamespace || fontSet', '[attr.fontIcon]': '_usingFontIcon() ? fontIcon : null', '[class.mat-icon-inline]': 'inline', '[class.mat-icon-no-color]': 'color !== "primary" && color !== "accent" && color !== "warn"', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["mat-icon,mat-icon.mat-primary,mat-icon.mat-accent,mat-icon.mat-warn{color:var(--mat-icon-color, inherit)}.mat-icon{-webkit-user-select:none;user-select:none;background-repeat:no-repeat;display:inline-block;fill:currentColor;height:24px;width:24px;overflow:hidden}.mat-icon.mat-icon-inline{font-size:inherit;height:inherit;line-height:inherit;width:inherit}.mat-icon.mat-ligature-font[fontIcon]::before{content:attr(fontIcon)}[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"] }] }], ctorParameters: () => [], propDecorators: { color: [{ type: Input }], inline: [{ type: Input, args: [{ transform: booleanAttribute }] }], svgIcon: [{ type: Input }], fontSet: [{ type: Input }], fontIcon: [{ type: Input }] } }); class MatIconModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, imports: [MatCommonModule, MatIcon], exports: [MatIcon, MatCommonModule] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, imports: [MatCommonModule, MatCommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, decorators: [{ type: NgModule, args: [{ imports: [MatCommonModule, MatIcon], exports: [MatIcon, MatCommonModule], }] }] }); export { MAT_ICON_DEFAULT_OPTIONS, MAT_ICON_LOCATION, MAT_ICON_LOCATION_FACTORY, MatIcon, MatIconModule, MatIconRegistry }; //# sourceMappingURL=icon.mjs.map