diff --git a/src/components-examples/material/icon/icon-harness/icon-harness-example.html b/src/components-examples/material/icon/icon-harness/icon-harness-example.html index 5769529ec1e6..57af4c464de2 100644 --- a/src/components-examples/material/icon/icon-harness/icon-harness-example.html +++ b/src/components-examples/material/icon/icon-harness/icon-harness-example.html @@ -1,3 +1,4 @@ ligature_icon + diff --git a/src/components-examples/material/icon/icon-harness/icon-harness-example.spec.ts b/src/components-examples/material/icon/icon-harness/icon-harness-example.spec.ts index 4b6f1c9d3dd1..c4cd4674cb7a 100644 --- a/src/components-examples/material/icon/icon-harness/icon-harness-example.spec.ts +++ b/src/components-examples/material/icon/icon-harness/icon-harness-example.spec.ts @@ -32,24 +32,24 @@ describe('IconHarnessExample', () => { it('should load all icon harnesses', async () => { const icons = await loader.getAllHarnesses(MatIconHarness); - expect(icons.length).toBe(3); + expect(icons.length).toBe(4); }); it('should get the name of an icon', async () => { const icons = await loader.getAllHarnesses(MatIconHarness); const names = await parallel(() => icons.map(icon => icon.getName())); - expect(names).toEqual(['fontIcon', 'svgIcon', 'ligature_icon']); + expect(names).toEqual(['fontIcon', 'svgIcon', 'ligature_icon', 'ligature_icon_by_attribute']); }); it('should get the namespace of an icon', async () => { const icons = await loader.getAllHarnesses(MatIconHarness); const namespaces = await parallel(() => icons.map(icon => icon.getNamespace())); - expect(namespaces).toEqual(['fontIcons', 'svgIcons', null]); + expect(namespaces).toEqual(['fontIcons', 'svgIcons', null, null]); }); it('should get whether an icon is inline', async () => { const icons = await loader.getAllHarnesses(MatIconHarness); const inlineStates = await parallel(() => icons.map(icon => icon.isInline())); - expect(inlineStates).toEqual([false, false, true]); + expect(inlineStates).toEqual([false, false, true, true]); }); }); diff --git a/src/components-examples/material/icon/icon-overview/icon-overview-example.html b/src/components-examples/material/icon/icon-overview/icon-overview-example.html index ac069beed3fd..3dd06c0c3017 100644 --- a/src/components-examples/material/icon/icon-overview/icon-overview-example.html +++ b/src/components-examples/material/icon/icon-overview/icon-overview-example.html @@ -1 +1 @@ -home + diff --git a/src/dev-app/icon/icon-demo.html b/src/dev-app/icon/icon-demo.html index aff0f54360d7..e8cfdd913a13 100644 --- a/src/dev-app/icon/icon-demo.html +++ b/src/dev-app/icon/icon-demo.html @@ -38,7 +38,12 @@

- Ligature from Material Icons font: + Ligature from Material Icons font by attribute: + +

+ +

+ Ligature from Material Icons font by content: home

diff --git a/src/material/icon/icon.scss b/src/material/icon/icon.scss index 66a37a418388..a1c592bc61da 100644 --- a/src/material/icon/icon.scss +++ b/src/material/icon/icon.scss @@ -21,6 +21,10 @@ $size: 24px !default; line-height: inherit; width: inherit; } + + &[icon]:before { + content: attr(icon); + } } // Icons that will be mirrored in RTL. diff --git a/src/material/icon/icon.spec.ts b/src/material/icon/icon.spec.ts index 7470ee040101..e383bc81ebb7 100644 --- a/src/material/icon/icon.spec.ts +++ b/src/material/icon/icon.spec.ts @@ -1,5 +1,5 @@ -import {inject, waitForAsync, fakeAsync, tick, TestBed} from '@angular/core/testing'; -import {SafeResourceUrl, DomSanitizer, SafeHtml} from '@angular/platform-browser'; +import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing'; +import {DomSanitizer, SafeHtml, SafeResourceUrl} from '@angular/platform-browser'; import { HttpClientTestingModule, HttpTestingController, @@ -7,7 +7,7 @@ import { } from '@angular/common/http/testing'; import {Component, ErrorHandler, Provider, Type, ViewChild} from '@angular/core'; import {MAT_ICON_DEFAULT_OPTIONS, MAT_ICON_LOCATION, MatIconModule} from './index'; -import {MatIconRegistry, getMatIconNoHttpProviderError} from './icon-registry'; +import {getMatIconNoHttpProviderError, MatIconRegistry} from './icon-registry'; import {FAKE_SVGS} from './fake-svgs'; import {wrappedErrorMessage} from '../../cdk/testing/private'; import {MatIcon} from './icon'; @@ -70,6 +70,7 @@ describe('MatIcon', () => { declarations: [ IconWithColor, IconWithLigature, + IconWithLigatureByAttribute, IconWithCustomFontCss, IconFromSvgName, IconWithAriaHiddenFalse, @@ -243,6 +244,58 @@ describe('MatIcon', () => { }); }); + describe('Ligature icons by attribute', () => { + it('should add material-icons class by default', () => { + const fixture = TestBed.createComponent(IconWithLigatureByAttribute); + + const testComponent = fixture.componentInstance; + const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon'); + testComponent.iconName = 'home'; + fixture.detectChanges(); + expect(sortedClassNames(matIconElement)).toEqual([ + 'mat-icon', + 'mat-icon-no-color', + 'material-icons', + 'notranslate', + ]); + }); + + it('should use alternate icon font if set', () => { + iconRegistry.setDefaultFontSetClass('myfont'); + + const fixture = TestBed.createComponent(IconWithLigatureByAttribute); + + const testComponent = fixture.componentInstance; + const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon'); + testComponent.iconName = 'home'; + fixture.detectChanges(); + expect(sortedClassNames(matIconElement)).toEqual([ + 'mat-icon', + 'mat-icon-no-color', + 'myfont', + 'notranslate', + ]); + }); + + it('should be able to provide multiple alternate icon set classes', () => { + iconRegistry.setDefaultFontSetClass('myfont', 'myfont-48x48'); + + let fixture = TestBed.createComponent(IconWithLigatureByAttribute); + + const testComponent = fixture.componentInstance; + const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon'); + testComponent.iconName = 'home'; + fixture.detectChanges(); + expect(sortedClassNames(matIconElement)).toEqual([ + 'mat-icon', + 'mat-icon-no-color', + 'myfont', + 'myfont-48x48', + 'notranslate', + ]); + }); + }); + describe('Icons from URLs', () => { it('should register icon URLs by name', fakeAsync(() => { iconRegistry.addSvgIcon('fluffy', trustUrl('cat.svg')); @@ -1338,6 +1391,11 @@ class IconWithLigature { iconName = ''; } +@Component({template: ``}) +class IconWithLigatureByAttribute { + iconName = ''; +} + @Component({template: `{{iconName}}`}) class IconWithColor { iconName = ''; diff --git a/src/material/icon/icon.ts b/src/material/icon/icon.ts index e65c4d7d6021..6f1c0e6c49b9 100644 --- a/src/material/icon/icon.ts +++ b/src/material/icon/icon.ts @@ -24,7 +24,7 @@ import { Optional, ViewEncapsulation, } from '@angular/core'; -import {CanColor, ThemePalette, mixinColor} from '@angular/material/core'; +import {CanColor, mixinColor, ThemePalette} from '@angular/material/core'; import {Subscription} from 'rxjs'; import {take} from 'rxjs/operators'; @@ -114,13 +114,16 @@ const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/; * ` * ` * - * - Use a font ligature as an icon by putting the ligature text in the content of the `` - * component. By default the Material icons font is used as described at + * - Use a font ligature as an icon by putting the ligature text in the `icon` attribute or the + * content of the `` component. 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: - * `home + * ` + * ` + * home * sun` * * - Specify a font glyph to be included via CSS rules by setting the fontSet input to specify the @@ -162,6 +165,13 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C } private _inline: boolean = false; + /** + * Name of an icon within a font set that use ligatures, such as the + * [Material icons font](http://google.github.io/material-design-icons/#icon-font-for-the-web). + */ + @Input() + icon: string; + /** Name of the icon in the SVG icon set. */ @Input() get svgIcon(): string { @@ -194,7 +204,10 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C } private _fontSet: string; - /** Name of an icon within a font set. */ + /** + * Name of an icon within a font set that use CSS class for each icon glyph, such as + * [FontAwesome](https://fortawesome.github.io/Font-Awesome/examples/). + */ @Input() get fontIcon(): string { return this._fontIcon; diff --git a/src/material/icon/testing/icon-harness.ts b/src/material/icon/testing/icon-harness.ts index eb3a90fc4b18..a3d6ce73ce61 100644 --- a/src/material/icon/testing/icon-harness.ts +++ b/src/material/icon/testing/icon-harness.ts @@ -40,11 +40,16 @@ export class MatIconHarness extends ComponentHarness { /** Gets the name of the icon. */ async getName(): Promise { const host = await this.host(); - const nameFromDom = await host.getAttribute('data-mat-icon-name'); // If we managed to figure out the name from the attribute, use it. - if (nameFromDom) { - return nameFromDom; + const nameFromIconNameAttribute = await host.getAttribute('data-mat-icon-name'); + if (nameFromIconNameAttribute) { + return nameFromIconNameAttribute; + } + + const nameFromIconAttribute = await host.getAttribute('data-mat-icon-name'); + if (nameFromIconAttribute) { + return nameFromIconAttribute; } // Some icons support defining the icon as a ligature. diff --git a/src/material/icon/testing/shared.spec.ts b/src/material/icon/testing/shared.spec.ts index 36c6c5ee9874..7f5d1d67c4df 100644 --- a/src/material/icon/testing/shared.spec.ts +++ b/src/material/icon/testing/shared.spec.ts @@ -37,7 +37,7 @@ export function runHarnessTests( it('should load all icon harnesses', async () => { const icons = await loader.getAllHarnesses(iconHarness); - expect(icons.length).toBe(3); + expect(icons.length).toBe(4); }); it('should filter icon harnesses based on their type', async () => { @@ -47,7 +47,7 @@ export function runHarnessTests( ]); expect(svgIcons.length).toBe(1); - expect(fontIcons.length).toBe(2); + expect(fontIcons.length).toBe(3); }); it('should filter icon harnesses based on their name', async () => { @@ -75,25 +75,25 @@ export function runHarnessTests( it('should get the type of each icon', async () => { const icons = await loader.getAllHarnesses(iconHarness); const types = await parallel(() => icons.map(icon => icon.getType())); - expect(types).toEqual([IconType.FONT, IconType.SVG, IconType.FONT]); + expect(types).toEqual([IconType.FONT, IconType.SVG, IconType.FONT, IconType.FONT]); }); it('should get the name of an icon', async () => { const icons = await loader.getAllHarnesses(iconHarness); const names = await parallel(() => icons.map(icon => icon.getName())); - expect(names).toEqual(['fontIcon', 'svgIcon', 'ligature_icon']); + expect(names).toEqual(['fontIcon', 'svgIcon', 'ligature_icon', 'ligature_icon_by_attribute']); }); it('should get the namespace of an icon', async () => { const icons = await loader.getAllHarnesses(iconHarness); const namespaces = await parallel(() => icons.map(icon => icon.getNamespace())); - expect(namespaces).toEqual(['fontIcons', 'svgIcons', null]); + expect(namespaces).toEqual(['fontIcons', 'svgIcons', null, null]); }); it('should get whether an icon is inline', async () => { const icons = await loader.getAllHarnesses(iconHarness); const inlineStates = await parallel(() => icons.map(icon => icon.isInline())); - expect(inlineStates).toEqual([false, false, true]); + expect(inlineStates).toEqual([false, false, true, true]); }); } @@ -102,6 +102,7 @@ export function runHarnessTests( ligature_icon + `, }) class IconHarnessTest {}