@@ -17,7 +17,12 @@ import {
1717 OnInit ,
1818 SimpleChanges ,
1919 ViewEncapsulation ,
20+ Optional ,
21+ InjectionToken ,
22+ inject ,
23+ Inject ,
2024} from '@angular/core' ;
25+ import { DOCUMENT } from '@angular/common' ;
2126import { CanColor , CanColorCtor , mixinColor } from '@angular/material/core' ;
2227import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
2328import { MatIconRegistry } from './icon-registry' ;
@@ -31,6 +36,53 @@ export class MatIconBase {
3136export const _MatIconMixinBase : CanColorCtor & typeof MatIconBase =
3237 mixinColor ( MatIconBase ) ;
3338
39+ /**
40+ * Injection token used to provide the current location to `MatIcon`.
41+ * Used to handle server-side rendering and to stub out during unit tests.
42+ * @docs -private
43+ */
44+ export const MAT_ICON_LOCATION = new InjectionToken < MatIconLocation > ( 'mat-icon-location' , {
45+ providedIn : 'root' ,
46+ factory : MAT_ICON_LOCATION_FACTORY
47+ } ) ;
48+
49+ /**
50+ * Stubbed out location for `MatIcon`.
51+ * @docs -private
52+ */
53+ export interface MatIconLocation {
54+ pathname : string ;
55+ }
56+
57+ /** @docs -private */
58+ export function MAT_ICON_LOCATION_FACTORY ( ) : MatIconLocation {
59+ const _document = inject ( DOCUMENT ) ;
60+ const pathname = ( _document && _document . location && _document . location . pathname ) || '' ;
61+ return { pathname} ;
62+ }
63+
64+
65+ /** SVG attributes that accept a FuncIRI (e.g. `url(<something>)`). */
66+ const funcIriAttributes = [
67+ 'clip-path' ,
68+ 'color-profile' ,
69+ 'src' ,
70+ 'cursor' ,
71+ 'fill' ,
72+ 'filter' ,
73+ 'marker' ,
74+ 'marker-start' ,
75+ 'marker-mid' ,
76+ 'marker-end' ,
77+ 'mask' ,
78+ 'stroke'
79+ ] ;
80+
81+ /** Selector that can be used to find all elements that are using a `FuncIRI`. */
82+ const funcIriAttributeSelector = funcIriAttributes . map ( attr => `[${ attr } ]` ) . join ( ', ' ) ;
83+
84+ /** Regex that can be used to extract the id out of a FuncIRI. */
85+ const funcIriPattern = / ^ u r l \( [ ' " ] ? # ( .* ?) [ ' " ] ? \) $ / ;
3486
3587/**
3688 * Component to display an icon. It can be used in the following ways:
@@ -113,7 +165,12 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
113165 constructor (
114166 elementRef : ElementRef < HTMLElement > ,
115167 private _iconRegistry : MatIconRegistry ,
116- @Attribute ( 'aria-hidden' ) ariaHidden : string ) {
168+ @Attribute ( 'aria-hidden' ) ariaHidden : string ,
169+ /**
170+ * @deprecated `location` parameter to be made required.
171+ * @breaking -change 8.0.0
172+ */
173+ @Optional ( ) @Inject ( MAT_ICON_LOCATION ) private _location ?: MatIconLocation ) {
117174 super ( elementRef ) ;
118175
119176 // If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is
@@ -192,6 +249,9 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
192249 styleTags [ i ] . textContent += ' ' ;
193250 }
194251
252+ // Note: we do this fix here, rather than the icon registry, because the
253+ // references have to point to the URL at the time that the icon was created.
254+ this . _prependCurrentPathToReferences ( svg ) ;
195255 this . _elementRef . nativeElement . appendChild ( svg ) ;
196256 }
197257
@@ -251,4 +311,32 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
251311 private _cleanupFontValue ( value : string ) {
252312 return typeof value === 'string' ? value . trim ( ) . split ( ' ' ) [ 0 ] : value ;
253313 }
314+
315+ /**
316+ * Prepends the current path to all elements that have an attribute pointing to a `FuncIRI`
317+ * reference. This is required because WebKit browsers require references to be prefixed with
318+ * the current path, if the page has a `base` tag.
319+ */
320+ private _prependCurrentPathToReferences ( element : SVGElement ) {
321+ // @breaking -change 8.0.0 Remove this null check once `_location` parameter is required.
322+ if ( ! this . _location ) {
323+ return ;
324+ }
325+
326+ const elementsWithFuncIri = element . querySelectorAll ( funcIriAttributeSelector ) ;
327+ const path = this . _location . pathname ? this . _location . pathname . split ( '#' ) [ 0 ] : '' ;
328+
329+ for ( let i = 0 ; i < elementsWithFuncIri . length ; i ++ ) {
330+ funcIriAttributes . forEach ( attr => {
331+ const value = elementsWithFuncIri [ i ] . getAttribute ( attr ) ;
332+ const match = value ? value . match ( funcIriPattern ) : null ;
333+
334+ if ( match ) {
335+ // Note the quotes inside the `url()`. They're important, because URLs pointing to named
336+ // router outlets can contain parentheses which will break if they aren't quoted.
337+ elementsWithFuncIri [ i ] . setAttribute ( attr , `url('${ path } #${ match [ 1 ] } ')` ) ;
338+ }
339+ } ) ;
340+ }
341+ }
254342}
0 commit comments