@@ -17,7 +17,12 @@ import {
17
17
OnInit ,
18
18
SimpleChanges ,
19
19
ViewEncapsulation ,
20
+ Optional ,
21
+ InjectionToken ,
22
+ inject ,
23
+ Inject ,
20
24
} from '@angular/core' ;
25
+ import { DOCUMENT } from '@angular/common' ;
21
26
import { CanColor , CanColorCtor , mixinColor } from '@angular/material/core' ;
22
27
import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
23
28
import { MatIconRegistry } from './icon-registry' ;
@@ -31,6 +36,53 @@ export class MatIconBase {
31
36
export const _MatIconMixinBase : CanColorCtor & typeof MatIconBase =
32
37
mixinColor ( MatIconBase ) ;
33
38
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 \( [ ' " ] ? # ( .* ?) [ ' " ] ? \) $ / ;
34
86
35
87
/**
36
88
* 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
113
165
constructor (
114
166
elementRef : ElementRef < HTMLElement > ,
115
167
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 ) {
117
174
super ( elementRef ) ;
118
175
119
176
// 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
192
249
styleTags [ i ] . textContent += ' ' ;
193
250
}
194
251
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 ) ;
195
255
this . _elementRef . nativeElement . appendChild ( svg ) ;
196
256
}
197
257
@@ -251,4 +311,32 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
251
311
private _cleanupFontValue ( value : string ) {
252
312
return typeof value === 'string' ? value . trim ( ) . split ( ' ' ) [ 0 ] : value ;
253
313
}
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
+ }
254
342
}
0 commit comments