icon.mjs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. import * as i0 from '@angular/core';
  2. import { InjectionToken, inject, ElementRef, ErrorHandler, HostAttributeToken, booleanAttribute, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, NgModule } from '@angular/core';
  3. import { DOCUMENT } from '@angular/common';
  4. import { Subscription } from 'rxjs';
  5. import { take } from 'rxjs/operators';
  6. import { M as MatIconRegistry } from './icon-registry-B2IMBfNA.mjs';
  7. 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';
  8. import { M as MatCommonModule } from './common-module-WayjW0Pb.mjs';
  9. import '@angular/common/http';
  10. import '@angular/platform-browser';
  11. import '@angular/cdk/a11y';
  12. import '@angular/cdk/bidi';
  13. /** Injection token to be used to override the default options for `mat-icon`. */
  14. const MAT_ICON_DEFAULT_OPTIONS = new InjectionToken('MAT_ICON_DEFAULT_OPTIONS');
  15. /**
  16. * Injection token used to provide the current location to `MatIcon`.
  17. * Used to handle server-side rendering and to stub out during unit tests.
  18. * @docs-private
  19. */
  20. const MAT_ICON_LOCATION = new InjectionToken('mat-icon-location', {
  21. providedIn: 'root',
  22. factory: MAT_ICON_LOCATION_FACTORY,
  23. });
  24. /**
  25. * @docs-private
  26. * @deprecated No longer used, will be removed.
  27. * @breaking-change 21.0.0
  28. */
  29. function MAT_ICON_LOCATION_FACTORY() {
  30. const _document = inject(DOCUMENT);
  31. const _location = _document ? _document.location : null;
  32. return {
  33. // Note that this needs to be a function, rather than a property, because Angular
  34. // will only resolve it once, but we want the current path on each call.
  35. getPathname: () => (_location ? _location.pathname + _location.search : ''),
  36. };
  37. }
  38. /** SVG attributes that accept a FuncIRI (e.g. `url(<something>)`). */
  39. const funcIriAttributes = [
  40. 'clip-path',
  41. 'color-profile',
  42. 'src',
  43. 'cursor',
  44. 'fill',
  45. 'filter',
  46. 'marker',
  47. 'marker-start',
  48. 'marker-mid',
  49. 'marker-end',
  50. 'mask',
  51. 'stroke',
  52. ];
  53. /** Selector that can be used to find all elements that are using a `FuncIRI`. */
  54. const funcIriAttributeSelector = funcIriAttributes.map(attr => `[${attr}]`).join(', ');
  55. /** Regex that can be used to extract the id out of a FuncIRI. */
  56. const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/;
  57. /**
  58. * Component to display an icon. It can be used in the following ways:
  59. *
  60. * - Specify the svgIcon input to load an SVG icon from a URL previously registered with the
  61. * addSvgIcon, addSvgIconInNamespace, addSvgIconSet, or addSvgIconSetInNamespace methods of
  62. * MatIconRegistry. If the svgIcon value contains a colon it is assumed to be in the format
  63. * "[namespace]:[name]", if not the value will be the name of an icon in the default namespace.
  64. * Examples:
  65. * `<mat-icon svgIcon="left-arrow"></mat-icon>
  66. * <mat-icon svgIcon="animals:cat"></mat-icon>`
  67. *
  68. * - Use a font ligature as an icon by putting the ligature text in the `fontIcon` attribute or the
  69. * content of the `<mat-icon>` component. If you register a custom font class, don't forget to also
  70. * include the special class `mat-ligature-font`. It is recommended to use the attribute alternative
  71. * to prevent the ligature text to be selectable and to appear in search engine results.
  72. * By default, the Material icons font is used as described at
  73. * http://google.github.io/material-design-icons/#icon-font-for-the-web. You can specify an
  74. * alternate font by setting the fontSet input to either the CSS class to apply to use the
  75. * desired font, or to an alias previously registered with MatIconRegistry.registerFontClassAlias.
  76. * Examples:
  77. * `<mat-icon fontIcon="home"></mat-icon>
  78. * <mat-icon>home</mat-icon>
  79. * <mat-icon fontSet="myfont" fontIcon="sun"></mat-icon>
  80. * <mat-icon fontSet="myfont">sun</mat-icon>`
  81. *
  82. * - Specify a font glyph to be included via CSS rules by setting the fontSet input to specify the
  83. * font, and the fontIcon input to specify the icon. Typically the fontIcon will specify a
  84. * CSS class which causes the glyph to be displayed via a :before selector, as in
  85. * https://fontawesome-v4.github.io/examples/
  86. * Example:
  87. * `<mat-icon fontSet="fa" fontIcon="alarm"></mat-icon>`
  88. */
  89. class MatIcon {
  90. _elementRef = inject(ElementRef);
  91. _iconRegistry = inject(MatIconRegistry);
  92. _location = inject(MAT_ICON_LOCATION);
  93. _errorHandler = inject(ErrorHandler);
  94. _defaultColor;
  95. /**
  96. * Theme color of the icon. This API is supported in M2 themes only, it
  97. * has no effect in M3 themes. For color customization in M3, see https://material.angular.dev/components/icon/styling.
  98. *
  99. * For information on applying color variants in M3, see
  100. * https://material.angular.dev/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants
  101. */
  102. get color() {
  103. return this._color || this._defaultColor;
  104. }
  105. set color(value) {
  106. this._color = value;
  107. }
  108. _color;
  109. /**
  110. * Whether the icon should be inlined, automatically sizing the icon to match the font size of
  111. * the element the icon is contained in.
  112. */
  113. inline = false;
  114. /** Name of the icon in the SVG icon set. */
  115. get svgIcon() {
  116. return this._svgIcon;
  117. }
  118. set svgIcon(value) {
  119. if (value !== this._svgIcon) {
  120. if (value) {
  121. this._updateSvgIcon(value);
  122. }
  123. else if (this._svgIcon) {
  124. this._clearSvgElement();
  125. }
  126. this._svgIcon = value;
  127. }
  128. }
  129. _svgIcon;
  130. /** Font set that the icon is a part of. */
  131. get fontSet() {
  132. return this._fontSet;
  133. }
  134. set fontSet(value) {
  135. const newValue = this._cleanupFontValue(value);
  136. if (newValue !== this._fontSet) {
  137. this._fontSet = newValue;
  138. this._updateFontIconClasses();
  139. }
  140. }
  141. _fontSet;
  142. /** Name of an icon within a font set. */
  143. get fontIcon() {
  144. return this._fontIcon;
  145. }
  146. set fontIcon(value) {
  147. const newValue = this._cleanupFontValue(value);
  148. if (newValue !== this._fontIcon) {
  149. this._fontIcon = newValue;
  150. this._updateFontIconClasses();
  151. }
  152. }
  153. _fontIcon;
  154. _previousFontSetClass = [];
  155. _previousFontIconClass;
  156. _svgName;
  157. _svgNamespace;
  158. /** Keeps track of the current page path. */
  159. _previousPath;
  160. /** Keeps track of the elements and attributes that we've prefixed with the current path. */
  161. _elementsWithExternalReferences;
  162. /** Subscription to the current in-progress SVG icon request. */
  163. _currentIconFetch = Subscription.EMPTY;
  164. constructor() {
  165. const ariaHidden = inject(new HostAttributeToken('aria-hidden'), { optional: true });
  166. const defaults = inject(MAT_ICON_DEFAULT_OPTIONS, { optional: true });
  167. if (defaults) {
  168. if (defaults.color) {
  169. this.color = this._defaultColor = defaults.color;
  170. }
  171. if (defaults.fontSet) {
  172. this.fontSet = defaults.fontSet;
  173. }
  174. }
  175. // If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is
  176. // the right thing to do for the majority of icon use-cases.
  177. if (!ariaHidden) {
  178. this._elementRef.nativeElement.setAttribute('aria-hidden', 'true');
  179. }
  180. }
  181. /**
  182. * Splits an svgIcon binding value into its icon set and icon name components.
  183. * Returns a 2-element array of [(icon set), (icon name)].
  184. * The separator for the two fields is ':'. If there is no separator, an empty
  185. * string is returned for the icon set and the entire value is returned for
  186. * the icon name. If the argument is falsy, returns an array of two empty strings.
  187. * Throws an error if the name contains two or more ':' separators.
  188. * Examples:
  189. * `'social:cake' -> ['social', 'cake']
  190. * 'penguin' -> ['', 'penguin']
  191. * null -> ['', '']
  192. * 'a:b:c' -> (throws Error)`
  193. */
  194. _splitIconName(iconName) {
  195. if (!iconName) {
  196. return ['', ''];
  197. }
  198. const parts = iconName.split(':');
  199. switch (parts.length) {
  200. case 1:
  201. return ['', parts[0]]; // Use default namespace.
  202. case 2:
  203. return parts;
  204. default:
  205. throw Error(`Invalid icon name: "${iconName}"`); // TODO: add an ngDevMode check
  206. }
  207. }
  208. ngOnInit() {
  209. // Update font classes because ngOnChanges won't be called if none of the inputs are present,
  210. // e.g. <mat-icon>arrow</mat-icon> In this case we need to add a CSS class for the default font.
  211. this._updateFontIconClasses();
  212. }
  213. ngAfterViewChecked() {
  214. const cachedElements = this._elementsWithExternalReferences;
  215. if (cachedElements && cachedElements.size) {
  216. const newPath = this._location.getPathname();
  217. // We need to check whether the URL has changed on each change detection since
  218. // the browser doesn't have an API that will let us react on link clicks and
  219. // we can't depend on the Angular router. The references need to be updated,
  220. // because while most browsers don't care whether the URL is correct after
  221. // the first render, Safari will break if the user navigates to a different
  222. // page and the SVG isn't re-rendered.
  223. if (newPath !== this._previousPath) {
  224. this._previousPath = newPath;
  225. this._prependPathToReferences(newPath);
  226. }
  227. }
  228. }
  229. ngOnDestroy() {
  230. this._currentIconFetch.unsubscribe();
  231. if (this._elementsWithExternalReferences) {
  232. this._elementsWithExternalReferences.clear();
  233. }
  234. }
  235. _usingFontIcon() {
  236. return !this.svgIcon;
  237. }
  238. _setSvgElement(svg) {
  239. this._clearSvgElement();
  240. // Note: we do this fix here, rather than the icon registry, because the
  241. // references have to point to the URL at the time that the icon was created.
  242. const path = this._location.getPathname();
  243. this._previousPath = path;
  244. this._cacheChildrenWithExternalReferences(svg);
  245. this._prependPathToReferences(path);
  246. this._elementRef.nativeElement.appendChild(svg);
  247. }
  248. _clearSvgElement() {
  249. const layoutElement = this._elementRef.nativeElement;
  250. let childCount = layoutElement.childNodes.length;
  251. if (this._elementsWithExternalReferences) {
  252. this._elementsWithExternalReferences.clear();
  253. }
  254. // Remove existing non-element child nodes and SVGs, and add the new SVG element. Note that
  255. // we can't use innerHTML, because IE will throw if the element has a data binding.
  256. while (childCount--) {
  257. const child = layoutElement.childNodes[childCount];
  258. // 1 corresponds to Node.ELEMENT_NODE. We remove all non-element nodes in order to get rid
  259. // of any loose text nodes, as well as any SVG elements in order to remove any old icons.
  260. if (child.nodeType !== 1 || child.nodeName.toLowerCase() === 'svg') {
  261. child.remove();
  262. }
  263. }
  264. }
  265. _updateFontIconClasses() {
  266. if (!this._usingFontIcon()) {
  267. return;
  268. }
  269. const elem = this._elementRef.nativeElement;
  270. const fontSetClasses = (this.fontSet
  271. ? this._iconRegistry.classNameForFontAlias(this.fontSet).split(/ +/)
  272. : this._iconRegistry.getDefaultFontSetClass()).filter(className => className.length > 0);
  273. this._previousFontSetClass.forEach(className => elem.classList.remove(className));
  274. fontSetClasses.forEach(className => elem.classList.add(className));
  275. this._previousFontSetClass = fontSetClasses;
  276. if (this.fontIcon !== this._previousFontIconClass &&
  277. !fontSetClasses.includes('mat-ligature-font')) {
  278. if (this._previousFontIconClass) {
  279. elem.classList.remove(this._previousFontIconClass);
  280. }
  281. if (this.fontIcon) {
  282. elem.classList.add(this.fontIcon);
  283. }
  284. this._previousFontIconClass = this.fontIcon;
  285. }
  286. }
  287. /**
  288. * Cleans up a value to be used as a fontIcon or fontSet.
  289. * Since the value ends up being assigned as a CSS class, we
  290. * have to trim the value and omit space-separated values.
  291. */
  292. _cleanupFontValue(value) {
  293. return typeof value === 'string' ? value.trim().split(' ')[0] : value;
  294. }
  295. /**
  296. * Prepends the current path to all elements that have an attribute pointing to a `FuncIRI`
  297. * reference. This is required because WebKit browsers require references to be prefixed with
  298. * the current path, if the page has a `base` tag.
  299. */
  300. _prependPathToReferences(path) {
  301. const elements = this._elementsWithExternalReferences;
  302. if (elements) {
  303. elements.forEach((attrs, element) => {
  304. attrs.forEach(attr => {
  305. element.setAttribute(attr.name, `url('${path}#${attr.value}')`);
  306. });
  307. });
  308. }
  309. }
  310. /**
  311. * Caches the children of an SVG element that have `url()`
  312. * references that we need to prefix with the current path.
  313. */
  314. _cacheChildrenWithExternalReferences(element) {
  315. const elementsWithFuncIri = element.querySelectorAll(funcIriAttributeSelector);
  316. const elements = (this._elementsWithExternalReferences =
  317. this._elementsWithExternalReferences || new Map());
  318. for (let i = 0; i < elementsWithFuncIri.length; i++) {
  319. funcIriAttributes.forEach(attr => {
  320. const elementWithReference = elementsWithFuncIri[i];
  321. const value = elementWithReference.getAttribute(attr);
  322. const match = value ? value.match(funcIriPattern) : null;
  323. if (match) {
  324. let attributes = elements.get(elementWithReference);
  325. if (!attributes) {
  326. attributes = [];
  327. elements.set(elementWithReference, attributes);
  328. }
  329. attributes.push({ name: attr, value: match[1] });
  330. }
  331. });
  332. }
  333. }
  334. /** Sets a new SVG icon with a particular name. */
  335. _updateSvgIcon(rawName) {
  336. this._svgNamespace = null;
  337. this._svgName = null;
  338. this._currentIconFetch.unsubscribe();
  339. if (rawName) {
  340. const [namespace, iconName] = this._splitIconName(rawName);
  341. if (namespace) {
  342. this._svgNamespace = namespace;
  343. }
  344. if (iconName) {
  345. this._svgName = iconName;
  346. }
  347. this._currentIconFetch = this._iconRegistry
  348. .getNamedSvgIcon(iconName, namespace)
  349. .pipe(take(1))
  350. .subscribe(svg => this._setSvgElement(svg), (err) => {
  351. const errorMessage = `Error retrieving icon ${namespace}:${iconName}! ${err.message}`;
  352. this._errorHandler.handleError(new Error(errorMessage));
  353. });
  354. }
  355. }
  356. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
  357. 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 });
  358. }
  359. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIcon, decorators: [{
  360. type: Component,
  361. args: [{ template: '<ng-content></ng-content>', selector: 'mat-icon', exportAs: 'matIcon', host: {
  362. 'role': 'img',
  363. 'class': 'mat-icon notranslate',
  364. '[class]': 'color ? "mat-" + color : ""',
  365. '[attr.data-mat-icon-type]': '_usingFontIcon() ? "font" : "svg"',
  366. '[attr.data-mat-icon-name]': '_svgName || fontIcon',
  367. '[attr.data-mat-icon-namespace]': '_svgNamespace || fontSet',
  368. '[attr.fontIcon]': '_usingFontIcon() ? fontIcon : null',
  369. '[class.mat-icon-inline]': 'inline',
  370. '[class.mat-icon-no-color]': 'color !== "primary" && color !== "accent" && color !== "warn"',
  371. }, 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"] }]
  372. }], ctorParameters: () => [], propDecorators: { color: [{
  373. type: Input
  374. }], inline: [{
  375. type: Input,
  376. args: [{ transform: booleanAttribute }]
  377. }], svgIcon: [{
  378. type: Input
  379. }], fontSet: [{
  380. type: Input
  381. }], fontIcon: [{
  382. type: Input
  383. }] } });
  384. class MatIconModule {
  385. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  386. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, imports: [MatCommonModule, MatIcon], exports: [MatIcon, MatCommonModule] });
  387. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, imports: [MatCommonModule, MatCommonModule] });
  388. }
  389. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatIconModule, decorators: [{
  390. type: NgModule,
  391. args: [{
  392. imports: [MatCommonModule, MatIcon],
  393. exports: [MatIcon, MatCommonModule],
  394. }]
  395. }] });
  396. export { MAT_ICON_DEFAULT_OPTIONS, MAT_ICON_LOCATION, MAT_ICON_LOCATION_FACTORY, MatIcon, MatIconModule, MatIconRegistry };
  397. //# sourceMappingURL=icon.mjs.map