mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(icon): initial md-icon implementation (#281)
This commit is contained in:
committed by
Jeremy Elbourn
parent
f8c8ae684a
commit
a094a33746
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Response,
|
||||
ResponseOptions} from 'angular2/http';
|
||||
|
||||
/**
|
||||
* Fake URLs and associated SVG documents used by tests.
|
||||
*/
|
||||
const FAKE_SVGS = (() => {
|
||||
const svgs = new Map<string, string>();
|
||||
svgs.set('cat.svg',
|
||||
'<svg><path id="meow"></path></svg>');
|
||||
|
||||
svgs.set('dog.svg',
|
||||
'<svg><path id="woof"></path></svg>');
|
||||
|
||||
svgs.set('farm-set-1.svg', `
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="pig"><path id="oink"></path></g>
|
||||
<g id="cow"><path id="moo"></path></g>
|
||||
</defs>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
svgs.set('farm-set-2.svg', `
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="cow"><path id="moo moo"></path></g>
|
||||
<g id="sheep"><path id="baa"></path></g>
|
||||
</defs>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
svgs.set('arrow-set.svg', `
|
||||
<svg>
|
||||
<defs>
|
||||
<svg id="left-arrow"><path id="left"></path></svg>
|
||||
<svg id="right-arrow"><path id="right"></path></svg>
|
||||
</defs>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
return svgs;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Returns an HTTP response for a fake SVG URL.
|
||||
*/
|
||||
export function getFakeSvgHttpResponse(url: string) {
|
||||
if (FAKE_SVGS.has(url)) {
|
||||
return new Response(new ResponseOptions({
|
||||
status: 200,
|
||||
body: FAKE_SVGS.get(url),
|
||||
}));
|
||||
} else {
|
||||
return new Response(new ResponseOptions({status: 404}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {Http} from 'angular2/http';
|
||||
import {Observable} from 'rxjs/Rx';
|
||||
|
||||
|
||||
/** Exception thrown when attempting to load an icon with a name that cannot be found. */
|
||||
export class MdIconNameNotFoundException extends BaseException {
|
||||
constructor(iconName: string) {
|
||||
super(`Unable to find icon with the name "${iconName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to load SVG content that does not contain the expected
|
||||
* <svg> tag.
|
||||
*/
|
||||
export class MdIconSvgTagNotFoundException extends BaseException {
|
||||
constructor() {
|
||||
super('<svg> tag not found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for an icon, including the URL and possibly the cached SVG element.
|
||||
* @internal
|
||||
*/
|
||||
class SvgIconConfig {
|
||||
svgElement: SVGElement = null;
|
||||
constructor(public url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the cache key to use for an icon namespace and name. */
|
||||
const iconKey = (namespace: string, name: string) => namespace + ':' + name;
|
||||
|
||||
/**
|
||||
* Service to register and display icons used by the <md-icon> component.
|
||||
* - Registers icon URLs by namespace and name.
|
||||
* - Registers icon set URLs by namespace.
|
||||
* - Registers aliases for CSS classes, for use with icon fonts.
|
||||
* - Loads icons from URLs and extracts individual icons from icon sets.
|
||||
*/
|
||||
@Injectable()
|
||||
export class MdIconRegistry {
|
||||
/**
|
||||
* URLs and cached SVG elements for individual icons. Keys are of the format "[namespace]:[icon]".
|
||||
*/
|
||||
private _svgIconConfigs = new Map<string, SvgIconConfig>();
|
||||
|
||||
/**
|
||||
* SvgIconConfig objects and cached SVG elements for icon sets, keyed by namespace.
|
||||
* Multiple icon sets can be registered under the same namespace.
|
||||
*/
|
||||
private _iconSetConfigs = new Map<string, SvgIconConfig[]>();
|
||||
|
||||
/** Cache for icons loaded by direct URLs. */
|
||||
private _cachedIconsByUrl = new Map<string, SVGElement>();
|
||||
|
||||
/** In-progress icon fetches. Used to coalesce multiple requests to the same URL. */
|
||||
private _inProgressUrlFetches = new Map<string, Observable<string>>();
|
||||
|
||||
/** Map from font identifiers to their CSS class names. Used for icon fonts. */
|
||||
private _fontCssClassesByAlias = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* The CSS class to apply when an <md-icon> component has no icon name, url, or font specified.
|
||||
* The default 'material-icons' value assumes that the material icon font has been loaded as
|
||||
* described at http://google.github.io/material-design-icons/#icon-font-for-the-web
|
||||
*/
|
||||
private _defaultFontSetClass = 'material-icons';
|
||||
|
||||
constructor(private _http: Http) {}
|
||||
|
||||
/** Registers an icon by URL in the default namespace. */
|
||||
addSvgIcon(iconName: string, url: string): this {
|
||||
return this.addSvgIconInNamespace('', iconName, url);
|
||||
}
|
||||
|
||||
/** Registers an icon by URL in the specified namespace. */
|
||||
addSvgIconInNamespace(namespace: string, iconName: string, url: string): this {
|
||||
const key = iconKey(namespace, iconName);
|
||||
this._svgIconConfigs.set(key, new SvgIconConfig(url));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Registers an icon set by URL in the default namespace. */
|
||||
addSvgIconSet(url: string): this {
|
||||
return this.addSvgIconSetInNamespace('', url);
|
||||
}
|
||||
|
||||
/** Registers an icon set by URL in the specified namespace. */
|
||||
addSvgIconSetInNamespace(namespace: string, url: string): this {
|
||||
const config = new SvgIconConfig(url);
|
||||
if (this._iconSetConfigs.has(namespace)) {
|
||||
this._iconSetConfigs.get(namespace).push(config);
|
||||
} else {
|
||||
this._iconSetConfigs.set(namespace, [config]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an alias for a CSS class name to be used for icon fonts. Creating an mdIcon
|
||||
* component with the alias as the fontSet input will cause the class name to be applied
|
||||
* to the <md-icon> element.
|
||||
*/
|
||||
registerFontClassAlias(alias: string, className = alias): this {
|
||||
this._fontCssClassesByAlias.set(alias, className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS class name associated with the alias by a previous call to
|
||||
* registerFontClassAlias. If no CSS class has been associated, returns the alias unmodified.
|
||||
*/
|
||||
classNameForFontAlias(alias: string): string {
|
||||
return this._fontCssClassesByAlias.get(alias) || alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS class name to be used for icon fonts when an <md-icon> component does not
|
||||
* have a fontSet input value, and is not loading an icon by name or URL.
|
||||
*/
|
||||
setDefaultFontSetClass(className: string): this {
|
||||
this._defaultFontSetClass = className;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS class name to be used for icon fonts when an <md-icon> component does not
|
||||
* have a fontSet input value, and is not loading an icon by name or URL.
|
||||
*/
|
||||
getDefaultFontSetClass(): string {
|
||||
return this._defaultFontSetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Observable that produces the icon (as an <svg> DOM element) from the given URL.
|
||||
* The response from the URL may be cached so this will not always cause an HTTP request, but
|
||||
* the produced element will always be a new copy of the originally fetched icon. (That is,
|
||||
* it will not contain any modifications made to elements previously returned).
|
||||
*/
|
||||
getSvgIconFromUrl(url: string): Observable<SVGElement> {
|
||||
if (this._cachedIconsByUrl.has(url)) {
|
||||
return Observable.of(this._cachedIconsByUrl.get(url).cloneNode(true));
|
||||
}
|
||||
return this._loadSvgIconFromConfig(new SvgIconConfig(url))
|
||||
.do(svg => this._cachedIconsByUrl.set(url, svg))
|
||||
.map(svg => svg.cloneNode(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Observable that produces the icon (as an <svg> DOM element) with the given name
|
||||
* and namespace. The icon must have been previously registered with addIcon or addIconSet;
|
||||
* if not, the Observable will throw an MdIconNameNotFoundException.
|
||||
*/
|
||||
getNamedSvgIcon(name: string, namespace = ''): Observable<SVGElement> {
|
||||
// Return (copy of) cached icon if possible.
|
||||
const key = iconKey(namespace, name);
|
||||
if (this._svgIconConfigs.has(key)) {
|
||||
return this._getSvgFromConfig(this._svgIconConfigs.get(key));
|
||||
}
|
||||
// See if we have any icon sets registered for the namespace.
|
||||
const iconSetConfigs = this._iconSetConfigs.get(namespace);
|
||||
if (iconSetConfigs) {
|
||||
return this._getSvgFromIconSetConfigs(name, iconSetConfigs);
|
||||
}
|
||||
return Observable.throw(new MdIconNameNotFoundException(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cached icon for a SvgIconConfig if available, or fetches it from its URL if not.
|
||||
*/
|
||||
private _getSvgFromConfig(config: SvgIconConfig): Observable<SVGElement> {
|
||||
if (config.svgElement) {
|
||||
// We already have the SVG element for this icon, return a copy.
|
||||
return Observable.of(config.svgElement.cloneNode(true));
|
||||
} else {
|
||||
// Fetch the icon from the config's URL, cache it, and return a copy.
|
||||
return this._loadSvgIconFromConfig(config)
|
||||
.do(svg => config.svgElement = svg)
|
||||
.map(svg => svg.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find an icon with the specified name in any of the SVG icon sets.
|
||||
* First searches the available cached icons for a nested element with a matching name, and
|
||||
* if found copies the element to a new <svg> element. If not found, fetches all icon sets
|
||||
* that have not been cached, and searches again after all fetches are completed.
|
||||
* The returned Observable produces the SVG element if possible, and throws
|
||||
* MdIconNameNotFoundException if no icon with the specified name can be found.
|
||||
*/
|
||||
private _getSvgFromIconSetConfigs(name: string, iconSetConfigs: SvgIconConfig[]):
|
||||
Observable<SVGElement> {
|
||||
// For all the icon set SVG elements we've fetched, see if any contain an icon with the
|
||||
// requested name.
|
||||
const namedIcon = this._extractIconWithNameFromAnySet(name, iconSetConfigs);
|
||||
if (namedIcon) {
|
||||
// We could cache namedIcon in _svgIconConfigs, but since we have to make a copy every
|
||||
// time anyway, there's probably not much advantage compared to just always extracting
|
||||
// it from the icon set.
|
||||
return Observable.of(namedIcon);
|
||||
}
|
||||
// Not found in any cached icon sets. If there are icon sets with URLs that we haven't
|
||||
// fetched, fetch them now and look for iconName in the results.
|
||||
const iconSetFetchRequests: Observable<SVGElement>[] = iconSetConfigs
|
||||
.filter(iconSetConfig => !iconSetConfig.svgElement)
|
||||
.map(iconSetConfig =>
|
||||
this._loadSvgIconSetFromConfig(iconSetConfig)
|
||||
.catch((err: any, source: any, caught: any): Observable<SVGElement> => {
|
||||
// Swallow errors fetching individual URLs so the combined Observable won't
|
||||
// necessarily fail.
|
||||
console.log(`Loading icon set URL: ${iconSetConfig.url} failed: ${err}`);
|
||||
return Observable.of(null);
|
||||
})
|
||||
.do(svg => {
|
||||
// Cache SVG element.
|
||||
if (svg) {
|
||||
iconSetConfig.svgElement = svg;
|
||||
}
|
||||
}));
|
||||
// Fetch all the icon set URLs. When the requests complete, every IconSet should have a
|
||||
// cached SVG element (unless the request failed), and we can check again for the icon.
|
||||
return Observable.forkJoin(iconSetFetchRequests)
|
||||
.map((ignoredResults: any) => {
|
||||
const foundIcon = this._extractIconWithNameFromAnySet(name, iconSetConfigs);
|
||||
if (!foundIcon) {
|
||||
throw new MdIconNameNotFoundException(name);
|
||||
}
|
||||
return foundIcon;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the cached SVG elements for the given icon sets for a nested icon element whose "id"
|
||||
* tag matches the specified name. If found, copies the nested element to a new SVG element and
|
||||
* returns it. Returns null if no matching element is found.
|
||||
*/
|
||||
private _extractIconWithNameFromAnySet(iconName: string, iconSetConfigs: SvgIconConfig[]):
|
||||
SVGElement {
|
||||
// Iterate backwards, so icon sets added later have precedence.
|
||||
for (let i = iconSetConfigs.length - 1; i >= 0; i--) {
|
||||
const config = iconSetConfigs[i];
|
||||
if (config.svgElement) {
|
||||
const foundIcon = this._extractSvgIconFromSet(config.svgElement, iconName, config);
|
||||
if (foundIcon) {
|
||||
return foundIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the content of the icon URL specified in the SvgIconConfig and creates an SVG element
|
||||
* from it.
|
||||
*/
|
||||
private _loadSvgIconFromConfig(config: SvgIconConfig): Observable<SVGElement> {
|
||||
return this._fetchUrl(config.url)
|
||||
.map(svgText => this._createSvgElementForSingleIcon(svgText, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the content of the icon set URL specified in the SvgIconConfig and creates an SVG element
|
||||
* from it.
|
||||
*/
|
||||
private _loadSvgIconSetFromConfig(config: SvgIconConfig): Observable<SVGElement> {
|
||||
// TODO: Document that icons should only be loaded from trusted sources.
|
||||
return this._fetchUrl(config.url)
|
||||
.map((svgText) => this._svgElementFromString(svgText));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOM element from the given SVG string, and adds default attributes.
|
||||
*/
|
||||
private _createSvgElementForSingleIcon(responseText: string, config: SvgIconConfig): SVGElement {
|
||||
const svg = this._svgElementFromString(responseText);
|
||||
this._setSvgAttributes(svg, config);
|
||||
return svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the cached element of the given SvgIconConfig for a nested icon element whose "id"
|
||||
* tag matches the specified name. If found, copies the nested element to a new SVG element and
|
||||
* returns it. Returns null if no matching element is found.
|
||||
*/
|
||||
private _extractSvgIconFromSet(
|
||||
iconSet: SVGElement, iconName: string, config: SvgIconConfig): SVGElement {
|
||||
const iconNode = iconSet.querySelector('#' + iconName);
|
||||
if (!iconNode) {
|
||||
return null;
|
||||
}
|
||||
// If the icon node is itself an <svg> node, clone and return it directly. If not, set it as
|
||||
// the content of a new <svg> node.
|
||||
if (iconNode.tagName.toLowerCase() == 'svg') {
|
||||
return this._setSvgAttributes(<SVGElement>iconNode.cloneNode(true), config);
|
||||
}
|
||||
// createElement('SVG') doesn't work as expected; the DOM ends up with
|
||||
// the correct nodes, but the SVG content doesn't render. Instead we
|
||||
// have to create an empty SVG node using innerHTML and append its content.
|
||||
// Elements created using DOMParser.parseFromString have the same problem.
|
||||
// http://stackoverflow.com/questions/23003278/svg-innerhtml-in-firefox-can-not-display
|
||||
const svg = this._svgElementFromString('<svg></svg>');
|
||||
// Clone the node so we don't remove it from the parent icon set element.
|
||||
svg.appendChild(iconNode.cloneNode(true));
|
||||
return this._setSvgAttributes(svg, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOM element from the given SVG string.
|
||||
*/
|
||||
private _svgElementFromString(str: string): SVGElement {
|
||||
// TODO: Is there a better way than innerHTML? Renderer doesn't appear to have a method for
|
||||
// creating an element from an HTML string.
|
||||
const div = document.createElement('DIV');
|
||||
div.innerHTML = str;
|
||||
const svg = <SVGElement>div.querySelector('svg');
|
||||
if (!svg) {
|
||||
throw new MdIconSvgTagNotFoundException();
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default attributes for an SVG element to be used as an icon.
|
||||
*/
|
||||
private _setSvgAttributes(svg: SVGElement, config: SvgIconConfig): SVGElement {
|
||||
if (!svg.getAttribute('xmlns')) {
|
||||
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||
}
|
||||
svg.setAttribute('fit', '');
|
||||
svg.setAttribute('height', '100%');
|
||||
svg.setAttribute('width', '100%');
|
||||
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
||||
svg.setAttribute('focusable', 'false'); // Disable IE11 default behavior to make SVGs focusable.
|
||||
return svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Observable which produces the string contents of the given URL. Results may be
|
||||
* cached, so future calls with the same URL may not cause another HTTP request.
|
||||
*/
|
||||
private _fetchUrl(url: string): Observable<string> {
|
||||
// Store in-progress fetches to avoid sending a duplicate request for a URL when there is
|
||||
// already a request in progress for that URL. It's necessary to call share() on the
|
||||
// Observable returned by http.get() so that multiple subscribers don't cause multiple XHRs.
|
||||
if (this._inProgressUrlFetches.has(url)) {
|
||||
return this._inProgressUrlFetches.get(url);
|
||||
}
|
||||
const req = this._http.get(url)
|
||||
.map((response) => response.text())
|
||||
.finally(() => {
|
||||
this._inProgressUrlFetches.delete(url);
|
||||
})
|
||||
.share();
|
||||
this._inProgressUrlFetches.set(url, req);
|
||||
return req;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
@import "variables";
|
||||
@import "default-theme";
|
||||
|
||||
/** The width/height of the icon element. */
|
||||
$md-icon-size: 24px !default;
|
||||
|
||||
/**
|
||||
This works because we're using ViewEncapsulation.None. If we used the default
|
||||
encapsulation, the selector would need to be ":host".
|
||||
*/
|
||||
md-icon {
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
height: $md-icon-size;
|
||||
margin: auto;
|
||||
vertical-align: middle;
|
||||
width: $md-icon-size;
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
import {
|
||||
it,
|
||||
describe,
|
||||
expect,
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
inject,
|
||||
TestComponentBuilder,
|
||||
} from 'angular2/testing';
|
||||
import {
|
||||
HTTP_PROVIDERS,
|
||||
XHRBackend} from 'angular2/http';
|
||||
import {MockBackend} from 'angular2/http/testing';
|
||||
import {
|
||||
provide,
|
||||
Component} from 'angular2/core';
|
||||
|
||||
import {MdIcon} from './icon';
|
||||
import {MdIconRegistry} from './icon-registry';
|
||||
import {getFakeSvgHttpResponse} from './fake-svgs';
|
||||
|
||||
/** Returns the CSS classes assigned to an element as a sorted array. */
|
||||
const sortedClassNames = (elem: Element) => elem.className.split(' ').sort();
|
||||
|
||||
/**
|
||||
* Verifies that an element contains a single <svg> child element, and returns that child.
|
||||
*/
|
||||
const verifyAndGetSingleSvgChild = (element: SVGElement): any => {
|
||||
expect(element.childNodes.length).toBe(1);
|
||||
const svgChild = <Element>element.childNodes[0];
|
||||
expect(svgChild.tagName.toLowerCase()).toBe('svg');
|
||||
return svgChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies that an element contains a single <path> child element whose "id" attribute has
|
||||
* the specified value.
|
||||
*/
|
||||
const verifyPathChildElement = (element: Element, attributeValue: string) => {
|
||||
expect(element.childNodes.length).toBe(1);
|
||||
const pathElement = <Element>element.childNodes[0];
|
||||
expect(pathElement.tagName.toLowerCase()).toBe('path');
|
||||
expect(pathElement.getAttribute('id')).toBe(attributeValue);
|
||||
};
|
||||
|
||||
export function main() {
|
||||
describe('MdIcon', () => {
|
||||
|
||||
beforeEachProviders(() => [
|
||||
MdIconRegistry,
|
||||
HTTP_PROVIDERS,
|
||||
MockBackend,
|
||||
provide(XHRBackend, {useExisting: MockBackend}),
|
||||
]);
|
||||
|
||||
let builder: TestComponentBuilder;
|
||||
let mdIconRegistry: MdIconRegistry;
|
||||
let httpRequestUrls: string[];
|
||||
|
||||
beforeEach(
|
||||
inject([TestComponentBuilder, MdIconRegistry, MockBackend],
|
||||
(tcb: TestComponentBuilder, mir: MdIconRegistry, mockBackend: MockBackend) => {
|
||||
builder = tcb;
|
||||
mdIconRegistry = mir;
|
||||
// Keep track of requests so we can verify caching behavior.
|
||||
// Return responses for the SVGs defined in fake-svgs.ts.
|
||||
httpRequestUrls = [];
|
||||
mockBackend.connections.subscribe((connection: any) => {
|
||||
const url = connection.request.url;
|
||||
httpRequestUrls.push(url);
|
||||
connection.mockRespond(getFakeSvgHttpResponse(url));
|
||||
});
|
||||
}));
|
||||
|
||||
describe('Ligature icons', () => {
|
||||
it('should add material-icons class by default', (done: () => void) => {
|
||||
return builder.createAsync(MdIconLigatureTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.iconName = 'home';
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassNames(mdIconElement)).toEqual(['material-icons']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use alternate icon font if set', (done: () => void) => {
|
||||
mdIconRegistry.setDefaultFontSetClass('myfont');
|
||||
return builder.createAsync(MdIconLigatureTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.iconName = 'home';
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassNames(mdIconElement)).toEqual(['myfont']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Icons from URLs', () => {
|
||||
it('should fetch SVG icon from URL and inline the content', (done: () => void) => {
|
||||
return builder.createAsync(MdIconFromSvgUrlTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
|
||||
testComponent.iconUrl = 'cat.svg';
|
||||
fixture.detectChanges();
|
||||
// An <svg> element should have been added as a child of <md-icon>.
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
// Default attributes should be set.
|
||||
expect(svgElement.getAttribute('height')).toBe('100%');
|
||||
expect(svgElement.getAttribute('height')).toBe('100%');
|
||||
// Make sure SVG content is taken from response.
|
||||
verifyPathChildElement(svgElement, 'meow');
|
||||
|
||||
// Change the icon, and the SVG element should be replaced.
|
||||
testComponent.iconUrl = 'dog.svg';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'woof');
|
||||
|
||||
expect(httpRequestUrls).toEqual(['cat.svg', 'dog.svg']);
|
||||
// Using an icon from a previously loaded URL should not cause another HTTP request.
|
||||
testComponent.iconUrl = 'cat.svg';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'meow');
|
||||
expect(httpRequestUrls).toEqual(['cat.svg', 'dog.svg']);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should register icon URLs by name', (done: () => void) => {
|
||||
mdIconRegistry.addSvgIcon('fluffy', 'cat.svg');
|
||||
mdIconRegistry.addSvgIcon('fido', 'dog.svg');
|
||||
return builder.createAsync(MdIconFromSvgNameTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: SVGElement;
|
||||
|
||||
testComponent.iconName = 'fido';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'woof');
|
||||
// The aria label should be taken from the icon name.
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('fido');
|
||||
|
||||
// Change the icon, and the SVG element should be replaced.
|
||||
testComponent.iconName = 'fluffy';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'meow');
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('fluffy');
|
||||
|
||||
expect(httpRequestUrls).toEqual(['dog.svg', 'cat.svg']);
|
||||
// Using an icon from a previously loaded URL should not cause another HTTP request.
|
||||
testComponent.iconName = 'fido';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'woof');
|
||||
expect(httpRequestUrls).toEqual(['dog.svg', 'cat.svg']);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should extract icon from SVG icon set', (done: () => void) => {
|
||||
mdIconRegistry.addSvgIconSetInNamespace('farm', 'farm-set-1.svg');
|
||||
return builder.createAsync(MdIconFromSvgNameTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
let svgChild: any;
|
||||
|
||||
testComponent.iconName = 'farm:pig';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mdIconElement.childNodes.length).toBe(1);
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
expect(svgElement.childNodes.length).toBe(1);
|
||||
svgChild = svgElement.childNodes[0];
|
||||
// The first <svg> child should be the <g id="pig"> element.
|
||||
expect(svgChild.tagName.toLowerCase()).toBe('g');
|
||||
expect(svgChild.getAttribute('id')).toBe('pig');
|
||||
verifyPathChildElement(svgChild, 'oink');
|
||||
// The aria label should be taken from the icon name (without the icon set portion).
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('pig');
|
||||
|
||||
// Change the icon, and the SVG element should be replaced.
|
||||
testComponent.iconName = 'farm:cow';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
svgChild = svgElement.childNodes[0];
|
||||
// The first <svg> child should be the <g id="cow"> element.
|
||||
expect(svgChild.tagName.toLowerCase()).toBe('g');
|
||||
expect(svgChild.getAttribute('id')).toBe('cow');
|
||||
verifyPathChildElement(svgChild, 'moo');
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('cow');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow multiple icon sets in a namespace', (done: () => void) => {
|
||||
mdIconRegistry.addSvgIconSetInNamespace('farm', 'farm-set-1.svg');
|
||||
mdIconRegistry.addSvgIconSetInNamespace('farm', 'farm-set-2.svg');
|
||||
mdIconRegistry.addSvgIconSetInNamespace('arrows', 'arrow-set.svg');
|
||||
return builder.createAsync(MdIconFromSvgNameTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
let svgChild: any;
|
||||
|
||||
testComponent.iconName = 'farm:pig';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
expect(svgElement.childNodes.length).toBe(1);
|
||||
svgChild = svgElement.childNodes[0];
|
||||
// The <svg> child should be the <g id="pig"> element.
|
||||
expect(svgChild.tagName.toLowerCase()).toBe('g');
|
||||
expect(svgChild.getAttribute('id')).toBe('pig');
|
||||
expect(svgChild.childNodes.length).toBe(1);
|
||||
verifyPathChildElement(svgChild, 'oink');
|
||||
// The aria label should be taken from the icon name (without the namespace).
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('pig');
|
||||
|
||||
// Both icon sets registered in the 'farm' namespace should have been fetched.
|
||||
expect(httpRequestUrls.sort()).toEqual(['farm-set-1.svg', 'farm-set-2.svg']);
|
||||
|
||||
// Change the icon name to one that appears in both icon sets. The icon from the set that
|
||||
// was registered last should be used (with id attribute of 'moo moo' instead of 'moo'),
|
||||
// and no additional HTTP request should be made.
|
||||
testComponent.iconName = 'farm:cow';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
svgChild = svgElement.childNodes[0];
|
||||
// The first <svg> child should be the <g id="cow"> element.
|
||||
expect(svgChild.tagName.toLowerCase()).toBe('g');
|
||||
expect(svgChild.getAttribute('id')).toBe('cow');
|
||||
expect(svgChild.childNodes.length).toBe(1);
|
||||
verifyPathChildElement(svgChild, 'moo moo');
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('cow');
|
||||
expect(httpRequestUrls.sort()).toEqual(['farm-set-1.svg', 'farm-set-2.svg']);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not wrap <svg> elements in icon sets in another svg tag', (done: () => void) => {
|
||||
mdIconRegistry.addSvgIconSet('arrow-set.svg');
|
||||
return builder.createAsync(MdIconFromSvgNameTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
|
||||
testComponent.iconName = 'left-arrow';
|
||||
fixture.detectChanges();
|
||||
// arrow-set.svg stores its icons as nested <svg> elements, so they should be used
|
||||
// directly and not wrapped in an outer <svg> tag like the <g> elements in other sets.
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'left');
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('left-arrow');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unmodified copies of icons from URLs', (done: () => void) => {
|
||||
return builder.createAsync(MdIconFromSvgUrlTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
|
||||
testComponent.iconUrl = 'cat.svg';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'meow');
|
||||
// Modify the SVG element by setting a viewBox attribute.
|
||||
svgElement.setAttribute('viewBox', '0 0 100 100');
|
||||
|
||||
// Switch to a different icon.
|
||||
testComponent.iconUrl = 'dog.svg';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'woof');
|
||||
|
||||
// Switch back to the first icon. The viewBox attribute should not be present.
|
||||
testComponent.iconUrl = 'cat.svg';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'meow');
|
||||
expect(svgElement.getAttribute('viewBox')).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unmodified copies of icons from icon sets', (done: () => void) => {
|
||||
mdIconRegistry.addSvgIconSet('arrow-set.svg');
|
||||
return builder.createAsync(MdIconFromSvgNameTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
let svgElement: any;
|
||||
|
||||
testComponent.iconName = 'left-arrow';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'left');
|
||||
// Modify the SVG element by setting a viewBox attribute.
|
||||
svgElement.setAttribute('viewBox', '0 0 100 100');
|
||||
|
||||
// Switch to a different icon.
|
||||
testComponent.iconName = 'right-arrow';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'right');
|
||||
|
||||
// Switch back to the first icon. The viewBox attribute should not be present.
|
||||
testComponent.iconName = 'left-arrow';
|
||||
fixture.detectChanges();
|
||||
svgElement = verifyAndGetSingleSvgChild(mdIconElement);
|
||||
verifyPathChildElement(svgElement, 'left');
|
||||
expect(svgElement.getAttribute('viewBox')).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom fonts', () => {
|
||||
it('should apply CSS classes for custom font and icon', (done: () => void) => {
|
||||
mdIconRegistry.registerFontClassAlias('f1', 'font1');
|
||||
mdIconRegistry.registerFontClassAlias('f2');
|
||||
return builder.createAsync(MdIconCustomFontCssTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.fontSet = 'f1';
|
||||
testComponent.fontIcon = 'house';
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassNames(mdIconElement)).toEqual(['font1', 'house']);
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('house');
|
||||
|
||||
testComponent.fontSet = 'f2';
|
||||
testComponent.fontIcon = 'igloo';
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassNames(mdIconElement)).toEqual(['f2', 'igloo']);
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('igloo');
|
||||
|
||||
testComponent.fontSet = 'f3';
|
||||
testComponent.fontIcon = 'tent';
|
||||
fixture.detectChanges();
|
||||
expect(sortedClassNames(mdIconElement)).toEqual(['f3', 'tent']);
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('tent');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria label', () => {
|
||||
it('should set aria label from text content if not specified', (done: () => void) => {
|
||||
return builder.createAsync(MdIconLigatureTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.iconName = 'home';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('home');
|
||||
|
||||
testComponent.iconName = 'hand';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('hand');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use alt tag if aria label is not specified', (done: () => void) => {
|
||||
return builder.createAsync(MdIconLigatureWithAriaBindingTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.iconName = 'home';
|
||||
testComponent.altText = 'castle';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('castle');
|
||||
|
||||
testComponent.ariaLabel = 'house';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('house');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use provided aria label rather than icon name', (done: () => void) => {
|
||||
return builder.createAsync(MdIconLigatureWithAriaBindingTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.iconName = 'home';
|
||||
testComponent.ariaLabel = 'house';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('house');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use provided aria label rather than font icon', (done: () => void) => {
|
||||
return builder.createAsync(MdIconCustomFontCssTestApp).then((fixture) => {
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const mdIconElement = fixture.debugElement.nativeElement.querySelector('md-icon');
|
||||
testComponent.fontSet = 'f1';
|
||||
testComponent.fontIcon = 'house';
|
||||
testComponent.ariaLabel = 'home';
|
||||
fixture.detectChanges();
|
||||
expect(mdIconElement.getAttribute('aria-label')).toBe('home');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Test components that contain an MdIcon. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `<md-icon>{{iconName}}</md-icon>`,
|
||||
directives: [MdIcon],
|
||||
})
|
||||
class MdIconLigatureTestApp {
|
||||
ariaLabel: string = null;
|
||||
iconName = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `<md-icon [aria-label]="ariaLabel" [alt]="altText">{{iconName}}</md-icon>`,
|
||||
directives: [MdIcon],
|
||||
})
|
||||
class MdIconLigatureWithAriaBindingTestApp {
|
||||
ariaLabel: string = null;
|
||||
iconName = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `
|
||||
<md-icon [fontSet]="fontSet" [fontIcon]="fontIcon" [aria-label]="ariaLabel"></md-icon>
|
||||
`,
|
||||
directives: [MdIcon],
|
||||
})
|
||||
class MdIconCustomFontCssTestApp {
|
||||
ariaLabel: string = null;
|
||||
fontSet = '';
|
||||
fontIcon = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `<md-icon [svgSrc]="iconUrl" [aria-label]="ariaLabel"></md-icon>`,
|
||||
directives: [MdIcon],
|
||||
})
|
||||
class MdIconFromSvgUrlTestApp {
|
||||
ariaLabel: string = null;
|
||||
iconUrl = '';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `<md-icon [svgIcon]="iconName" [aria-label]="ariaLabel"></md-icon>`,
|
||||
directives: [MdIcon],
|
||||
})
|
||||
class MdIconFromSvgNameTestApp {
|
||||
ariaLabel: string = null;
|
||||
iconName = '';
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import {
|
||||
AfterContentChecked,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Renderer,
|
||||
SimpleChange,
|
||||
ViewEncapsulation,
|
||||
} from 'angular2/core';
|
||||
import {NgClass} from 'angular2/common';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
|
||||
import {MdIconRegistry} from './icon-registry';
|
||||
|
||||
|
||||
/** Exception thrown when an invalid icon name is passed to an md-icon component. */
|
||||
export class MdIconInvalidNameException extends BaseException {
|
||||
constructor(iconName: string) {
|
||||
super(`Invalid icon name: "${name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to display an icon. It can be used in the following ways:
|
||||
* - Specify the svgSrc input to load an SVG icon from a URL. The SVG content is directly inlined
|
||||
* as a child of the <md-icon> component, so that CSS styles can easily be applied to it.
|
||||
* The URL is loaded via an XMLHttpRequest, so it must be on the same domain as the page or its
|
||||
* server must be configured to allow cross-domain requests.
|
||||
* Example:
|
||||
* <md-icon svgSrc="assets/arrow.svg"></md-icon>
|
||||
*
|
||||
* - Specify the svgIcon input to load an SVG icon from a URL previously registered with the
|
||||
* addSvgIcon, addSvgIconInNamespace, addSvgIconSet, or addSvgIconSetInNamespace methods of
|
||||
* MdIconRegistry. 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:
|
||||
* <md-icon svgIcon="left-arrow"></md-icon>
|
||||
* <md-icon svgIcon="animals:cat"></md-icon>
|
||||
*
|
||||
* - Use a font ligature as an icon by putting the ligature text in the content of the <md-icon>
|
||||
* component. 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 MdIconRegistry.registerFontClassAlias.
|
||||
* Examples:
|
||||
* <md-icon>home</md-icon>
|
||||
* <md-icon fontSet="myfont">sun</md-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://fortawesome.github.io/Font-Awesome/examples/
|
||||
* Example:
|
||||
* <md-icon fontSet="fa" fontIcon="alarm"></md-icon>
|
||||
*/
|
||||
@Component({
|
||||
template: '<ng-content></ng-content>',
|
||||
selector: 'md-icon',
|
||||
styleUrls: ['./components/icon/icon.css'],
|
||||
host: {
|
||||
'role': 'img',
|
||||
},
|
||||
directives: [NgClass],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
|
||||
@Input() svgSrc: string;
|
||||
@Input() svgIcon: string;
|
||||
@Input() fontSet: string;
|
||||
@Input() fontIcon: string;
|
||||
@Input() alt: string;
|
||||
|
||||
@Input('aria-label') hostAriaLabel: string = '';
|
||||
|
||||
private _previousFontSetClass: string;
|
||||
private _previousFontIconClass: string;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef,
|
||||
private _renderer: Renderer,
|
||||
private _mdIconRegistry: MdIconRegistry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 a MdIconInvalidNameException if the name contains two or more ':' separators.
|
||||
* Examples:
|
||||
* 'social:cake' -> ['social', 'cake']
|
||||
* 'penguin' -> ['', 'penguin']
|
||||
* null -> ['', '']
|
||||
* 'a:b:c' -> (throws MdIconInvalidNameException)
|
||||
*/
|
||||
private _splitIconName(iconName: string): [string, string] {
|
||||
if (!iconName) {
|
||||
return ['', ''];
|
||||
}
|
||||
const parts = iconName.split(':');
|
||||
switch (parts.length) {
|
||||
case 1:
|
||||
// Use default namespace.
|
||||
return ['', parts[0]];
|
||||
case 2:
|
||||
return <[string, string]>parts;
|
||||
default:
|
||||
throw new MdIconInvalidNameException(iconName);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
|
||||
const changedInputs = Object.keys(changes);
|
||||
// Only update the inline SVG icon if the inputs changed, to avoid unnecessary DOM operations.
|
||||
if (changedInputs.indexOf('svgIcon') != -1 || changedInputs.indexOf('svgSrc') != -1) {
|
||||
if (this.svgIcon) {
|
||||
const [namespace, iconName] = this._splitIconName(this.svgIcon);
|
||||
this._mdIconRegistry.getNamedSvgIcon(iconName, namespace).subscribe(
|
||||
svg => this._setSvgElement(svg),
|
||||
(err: any) => console.log(`Error retrieving icon: ${err}`));
|
||||
} else if (this.svgSrc) {
|
||||
this._mdIconRegistry.getSvgIconFromUrl(this.svgSrc).subscribe(
|
||||
svg => this._setSvgElement(svg),
|
||||
(err: any) => console.log(`Error retrieving icon: ${err}`));
|
||||
}
|
||||
}
|
||||
if (this._usingFontIcon()) {
|
||||
this._updateFontIconClasses();
|
||||
}
|
||||
this._updateAriaLabel();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// Update font classes because ngOnChanges won't be called if none of the inputs are present,
|
||||
// e.g. <md-icon>arrow</md-icon>. In this case we need to add a CSS class for the default font.
|
||||
if (this._usingFontIcon()) {
|
||||
this._updateFontIconClasses();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
// Update aria label here because it may depend on the projected text content.
|
||||
// (e.g. <md-icon>home</md-icon> should use 'home').
|
||||
this._updateAriaLabel();
|
||||
}
|
||||
|
||||
private _updateAriaLabel() {
|
||||
const ariaLabel = this._getAriaLabel();
|
||||
if (ariaLabel) {
|
||||
this._renderer.setElementAttribute(this._element.nativeElement, 'aria-label', ariaLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private _getAriaLabel() {
|
||||
// If the parent provided an aria-label attribute value, use it as-is. Otherwise look for a
|
||||
// reasonable value from the alt attribute, font icon name, SVG icon name, or (for ligatures)
|
||||
// the text content of the directive.
|
||||
const label =
|
||||
this.hostAriaLabel ||
|
||||
this.alt ||
|
||||
this.fontIcon ||
|
||||
this._splitIconName(this.svgIcon)[1];
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
// The "content" of an SVG icon is not a useful label.
|
||||
if (this._usingFontIcon()) {
|
||||
const text = this._element.nativeElement.textContent;
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
// TODO: Warn here in dev mode.
|
||||
return null;
|
||||
}
|
||||
|
||||
private _usingFontIcon(): boolean {
|
||||
return !(this.svgIcon || this.svgSrc);
|
||||
}
|
||||
|
||||
private _setSvgElement(svg: SVGElement) {
|
||||
const layoutElement = this._element.nativeElement;
|
||||
// Remove existing child nodes and add the new SVG element.
|
||||
// We would use renderer.detachView(Array.from(layoutElement.childNodes)) here,
|
||||
// but it fails in IE11: https://github.com/angular/angular/issues/6327
|
||||
layoutElement.innerHTML = '';
|
||||
this._renderer.projectNodes(layoutElement, [svg]);
|
||||
}
|
||||
|
||||
private _updateFontIconClasses() {
|
||||
if (!this._usingFontIcon()) {
|
||||
return;
|
||||
}
|
||||
const elem = this._element.nativeElement;
|
||||
const fontSetClass = this.fontSet ?
|
||||
this._mdIconRegistry.classNameForFontAlias(this.fontSet) :
|
||||
this._mdIconRegistry.getDefaultFontSetClass();
|
||||
if (fontSetClass != this._previousFontSetClass) {
|
||||
if (this._previousFontSetClass) {
|
||||
this._renderer.setElementClass(elem, this._previousFontSetClass, false);
|
||||
}
|
||||
if (fontSetClass) {
|
||||
this._renderer.setElementClass(elem, fontSetClass, true);
|
||||
}
|
||||
this._previousFontSetClass = fontSetClass;
|
||||
}
|
||||
|
||||
if (this.fontIcon != this._previousFontIconClass) {
|
||||
if (this._previousFontIconClass) {
|
||||
this._renderer.setElementClass(elem, this._previousFontIconClass, false);
|
||||
}
|
||||
if (this.fontIcon) {
|
||||
this._renderer.setElementClass(elem, this.fontIcon, true);
|
||||
}
|
||||
this._previousFontIconClass = this.fontIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<a md-list-item [routerLink]="['CheckboxDemo']">Checkbox</a>
|
||||
<a md-list-item [routerLink]="['GesturesDemo']">Gestures</a>
|
||||
<a md-list-item [routerLink]="['GridListDemo']">Grid List</a>
|
||||
<a md-list-item [routerLink]="['IconDemo']">Icon</a>
|
||||
<a md-list-item [routerLink]="['InputDemo']">Input</a>
|
||||
<a md-list-item [routerLink]="['ListDemo']">List</a>
|
||||
<a md-list-item [routerLink]="['LiveAnnouncerDemo']">Live Announcer</a>
|
||||
@@ -22,7 +23,7 @@
|
||||
<div>
|
||||
|
||||
<md-toolbar color="primary">
|
||||
<i class="material-icons" (click)="start.open()">menu</i>
|
||||
<md-icon (click)="start.open()">menu</md-icon>
|
||||
<div class="demo-toolbar">
|
||||
<h1>Angular Material 2 Demos</h1>
|
||||
<button md-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')" title="Toggle between RTL and LTR">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
md-toolbar {
|
||||
i {
|
||||
md-icon {
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {Component} from 'angular2/core';
|
||||
import {Route, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
import {CardDemo} from './card/card-demo';
|
||||
import {ButtonDemo} from './button/button-demo';
|
||||
import {IconDemo} from './icon/icon-demo';
|
||||
import {RadioDemo} from './radio/radio-demo';
|
||||
import {SidenavDemo} from './sidenav/sidenav-demo';
|
||||
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
|
||||
@@ -11,6 +12,7 @@ import {Dir} from '../core/rtl/dir';
|
||||
import {MdButton} from '../components/button/button';
|
||||
import {MD_SIDENAV_DIRECTIVES} from '../components/sidenav/sidenav';
|
||||
import {MD_LIST_DIRECTIVES} from '../components/list/list';
|
||||
import {MdIcon} from '../components/icon/icon';
|
||||
import {MdToolbar} from '../components/toolbar/toolbar';
|
||||
import {PortalDemo} from './portal/portal-demo';
|
||||
import {ToolbarDemo} from './toolbar/toolbar-demo';
|
||||
@@ -39,6 +41,7 @@ export class Home {}
|
||||
ROUTER_DIRECTIVES,
|
||||
Dir,
|
||||
MdButton,
|
||||
MdIcon,
|
||||
MD_SIDENAV_DIRECTIVES,
|
||||
MD_LIST_DIRECTIVES,
|
||||
MdToolbar,
|
||||
@@ -58,6 +61,7 @@ export class Home {}
|
||||
new Route({path: '/checkbox', name: 'CheckboxDemo', component: CheckboxDemo}),
|
||||
new Route({path: '/input', name: 'InputDemo', component: InputDemo}),
|
||||
new Route({path: '/toolbar', name: 'ToolbarDemo', component: ToolbarDemo}),
|
||||
new Route({path: '/icon', name: 'IconDemo', component: IconDemo}),
|
||||
new Route({path: '/list', name: 'ListDemo', component: ListDemo}),
|
||||
new Route({path: '/live-announcer', name: 'LiveAnnouncerDemo', component: LiveAnnouncerDemo}),
|
||||
new Route({path: '/gestures', name: 'GesturesDemo', component: GesturesDemo}),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<svg><defs>
|
||||
<svg id="3d-rotation"><path d="M7.52 21.48C4.25 19.94 1.91 16.76 1.55 13H.05C.56 19.16 5.71 24 12 24l.66-.03-3.81-3.81-1.33 1.32zm.89-6.52c-.19 0-.37-.03-.52-.08-.16-.06-.29-.13-.4-.24-.11-.1-.2-.22-.26-.37-.06-.14-.09-.3-.09-.47h-1.3c0 .36.07.68.21.95.14.27.33.5.56.69.24.18.51.32.82.41.3.1.62.15.96.15.37 0 .72-.05 1.03-.15.32-.1.6-.25.83-.44s.42-.43.55-.72c.13-.29.2-.61.2-.97 0-.19-.02-.38-.07-.56-.05-.18-.12-.35-.23-.51-.1-.16-.24-.3-.4-.43-.17-.13-.37-.23-.61-.31.2-.09.37-.2.52-.33.15-.13.27-.27.37-.42.1-.15.17-.3.22-.46.05-.16.07-.32.07-.48 0-.36-.06-.68-.18-.96-.12-.28-.29-.51-.51-.69-.2-.19-.47-.33-.77-.43C9.1 8.05 8.76 8 8.39 8c-.36 0-.69.05-1 .16-.3.11-.57.26-.79.45-.21.19-.38.41-.51.67-.12.26-.18.54-.18.85h1.3c0-.17.03-.32.09-.45s.14-.25.25-.34c.11-.09.23-.17.38-.22.15-.05.3-.08.48-.08.4 0 .7.1.89.31.19.2.29.49.29.86 0 .18-.03.34-.08.49-.05.15-.14.27-.25.37-.11.1-.25.18-.41.24-.16.06-.36.09-.58.09H7.5v1.03h.77c.22 0 .42.02.6.07s.33.13.45.23c.12.11.22.24.29.4.07.16.1.35.1.57 0 .41-.12.72-.35.93-.23.23-.55.33-.95.33zm8.55-5.92c-.32-.33-.7-.59-1.14-.77-.43-.18-.92-.27-1.46-.27H12v8h2.3c.55 0 1.06-.09 1.51-.27.45-.18.84-.43 1.16-.76.32-.33.57-.73.74-1.19.17-.47.26-.99.26-1.57v-.4c0-.58-.09-1.1-.26-1.57-.18-.47-.43-.87-.75-1.2zm-.39 3.16c0 .42-.05.79-.14 1.13-.1.33-.24.62-.43.85-.19.23-.43.41-.71.53-.29.12-.62.18-.99.18h-.91V9.12h.97c.72 0 1.27.23 1.64.69.38.46.57 1.12.57 1.99v.4zM12 0l-.66.03 3.81 3.81 1.33-1.33c3.27 1.55 5.61 4.72 5.96 8.48h1.5C23.44 4.84 18.29 0 12 0z"/></svg>
|
||||
<svg id="accessibility"><path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"/></svg>
|
||||
<svg id="account-balance"><path d="M4 10v7h3v-7H4zm6 0v7h3v-7h-3zM2 22h19v-3H2v3zm14-12v7h3v-7h-3zm-4.5-9L2 6v2h19V6l-9.5-5z"/></svg>
|
||||
<svg id="account-balance-wallet"><path d="M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></svg>
|
||||
<svg id="account-box"><path d="M3 5v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2zm12 4c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3zm-9 8c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6v-1z"/></svg>
|
||||
<svg id="account-child"><circle cx="12" cy="13.49" r="1.5"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 2.5c1.24 0 2.25 1.01 2.25 2.25S13.24 9 12 9 9.75 7.99 9.75 6.75 10.76 4.5 12 4.5zm5 10.56v2.5c-.45.41-.96.77-1.5 1.05v-.68c0-.34-.17-.65-.46-.92-.65-.62-1.89-1.02-3.04-1.02-.96 0-1.96.28-2.65.73l-.17.12-.21.17c.78.47 1.63.72 2.54.82l1.33.15c.37.04.66.36.66.75 0 .29-.16.53-.4.66-.28.15-.64.09-.95.09-.35 0-.69-.01-1.03-.05-.5-.06-.99-.17-1.46-.33-.49-.16-.97-.38-1.42-.64-.22-.13-.44-.27-.65-.43l-.31-.24c-.04-.02-.28-.18-.28-.23v-4.28c0-1.58 2.63-2.78 5-2.78s5 1.2 5 2.78v1.78z"/></svg>
|
||||
<svg id="account-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>
|
||||
<svg id="add"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<svg id="add-box"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>
|
||||
<svg id="add-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>
|
||||
<svg id="add-circle-outline"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
||||
<svg id="add-shopping-cart"><path d="M11 9h2V6h3V4h-3V1h-2v3H8v2h3v3zm-4 9c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.83-3.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.13 0-.25-.11-.25-.25z"/></svg>
|
||||
<svg id="alarm"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>
|
||||
<svg id="alarm-add"><path d="M7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm1-11h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"/></svg>
|
||||
<svg id="alarm-off"><path d="M12 6c3.87 0 7 3.13 7 7 0 .84-.16 1.65-.43 2.4l1.52 1.52c.58-1.19.91-2.51.91-3.92 0-4.97-4.03-9-9-9-1.41 0-2.73.33-3.92.91L9.6 6.43C10.35 6.16 11.16 6 12 6zm10-.28l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM2.92 2.29L1.65 3.57 2.98 4.9l-1.11.93 1.42 1.42 1.11-.94.8.8C3.83 8.69 3 10.75 3 13c0 4.97 4.02 9 9 9 2.25 0 4.31-.83 5.89-2.2l2.2 2.2 1.27-1.27L3.89 3.27l-.97-.98zm13.55 16.1C15.26 19.39 13.7 20 12 20c-3.87 0-7-3.13-7-7 0-1.7.61-3.26 1.61-4.47l9.86 9.86zM8.02 3.28L6.6 1.86l-.86.71 1.42 1.42.86-.71z"/></svg>
|
||||
<svg id="alarm-on"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm-1.46-5.47L8.41 12.4l-1.06 1.06 3.18 3.18 6-6-1.06-1.06-4.93 4.95z"/></svg>
|
||||
<svg id="android"><path d="M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0-1.99-.97-3.75-2.47-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z"/></svg>
|
||||
<svg id="announcement"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 9h-2V5h2v6zm0 4h-2v-2h2v2z"/></svg>
|
||||
<svg id="apps"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"/></svg>
|
||||
<svg id="archive"><path d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"/></svg>
|
||||
<svg id="arrow-back"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
||||
<svg id="arrow-drop-down"><path d="M7 10l5 5 5-5z"/></svg>
|
||||
<svg id="arrow-drop-down-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 12l-4-4h8l-4 4z"/></svg>
|
||||
<svg id="arrow-drop-up"><path d="M7 14l5-5 5 5z"/></svg>
|
||||
<svg id="cached"><path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"/></svg>
|
||||
<svg id="check"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
<svg id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>
|
||||
<svg id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/></svg>
|
||||
<svg id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
|
||||
<svg id="content-cut"><path d="M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3v-1L9.64 7.64zM6 8c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm0 12c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm6-7.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5.5.22.5.5-.22.5-.5.5zM19 3l-6 6 2 2 7-7V3z"/></svg>
|
||||
<svg id="content-paste"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"/></svg>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 341 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1,33 @@
|
||||
<div class="demo-icon">
|
||||
<p>
|
||||
These are some icons.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
From URL:
|
||||
<md-icon svgSrc="/demo-app/icon/assets/search-icon.svg"></md-icon>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By name registered with MdIconProvider:
|
||||
<md-icon svgIcon="thumb-up" class="green"></md-icon>
|
||||
<md-icon svgIcon="thumb-up"></md-icon>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
From icon set:
|
||||
<md-icon svgIcon="core:alarm"></md-icon>
|
||||
<md-icon svgIcon="core:accessibility"></md-icon>
|
||||
<md-icon svgIcon="core:alarm"></md-icon>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Ligature from Material Icons font:
|
||||
<md-icon>home</md-icon>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Custom icon font and CSS:
|
||||
<md-icon fontSet="fontawesome" fontIcon="fa-birthday-cake"></md-icon>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
.demo-icon md-icon {
|
||||
color: purple;
|
||||
}
|
||||
|
||||
.demo-icon md-icon.green {
|
||||
color: green;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {Component, ViewEncapsulation} from 'angular2/core';
|
||||
import {MdIcon} from '../../components/icon/icon';
|
||||
import {MdIconRegistry} from '../../components/icon/icon-registry';
|
||||
|
||||
@Component({
|
||||
selector: 'md-icon-demo',
|
||||
templateUrl: 'demo-app/icon/icon-demo.html',
|
||||
styleUrls: ['demo-app/icon/icon-demo.css'],
|
||||
directives: [MdIcon],
|
||||
viewProviders: [MdIconRegistry],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class IconDemo {
|
||||
constructor(mdIconRegistry: MdIconRegistry) {
|
||||
mdIconRegistry
|
||||
.addSvgIcon('thumb-up', '/demo-app/icon/assets/thumbup-icon.svg')
|
||||
.addSvgIconSetInNamespace('core', '/demo-app/icon/assets/core-icon-set.svg')
|
||||
.registerFontClassAlias('fontawesome', 'fa');
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="main.css" rel="stylesheet">
|
||||
<!-- FontAwesome for md-icon demo. -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<demo-app>Loading...</demo-app>
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {HAMMER_GESTURE_CONFIG} from 'angular2/src/platform/browser_common';
|
||||
import {DemoApp} from './demo-app/demo-app';
|
||||
import {HTTP_PROVIDERS} from 'angular2/http';
|
||||
import {ROUTER_PROVIDERS} from 'angular2/router';
|
||||
import {MdIconRegistry} from './components/icon/icon-registry';
|
||||
import {OVERLAY_CONTAINER_TOKEN} from './core/overlay/overlay';
|
||||
import {MdLiveAnnouncer} from './core/live-announcer/live-announcer';
|
||||
import {provide} from 'angular2/core';
|
||||
import {createOverlayContainer} from './core/overlay/overlay-container';
|
||||
import {Renderer} from 'angular2/core';
|
||||
import {MdGestureConfig} from './core/gestures/MdGestureConfig';
|
||||
import 'rxjs/Rx';
|
||||
|
||||
bootstrap(DemoApp, [
|
||||
ROUTER_PROVIDERS,
|
||||
MdLiveAnnouncer,
|
||||
provide(OVERLAY_CONTAINER_TOKEN, {useValue: createOverlayContainer()}),
|
||||
HTTP_PROVIDERS,
|
||||
MdIconRegistry,
|
||||
Renderer,
|
||||
provide(HAMMER_GESTURE_CONFIG, {useClass: MdGestureConfig})
|
||||
]);
|
||||
|
||||
@@ -30,6 +30,7 @@ export function config(config) {
|
||||
{pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: false},
|
||||
'node_modules/reflect-metadata/Reflect.js',
|
||||
{pattern: 'node_modules/angular2/bundles/angular2.dev.js', included: true, watched: false},
|
||||
{pattern: 'node_modules/angular2/bundles/http.dev.js', included: true, watched: false},
|
||||
{pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: false},
|
||||
|
||||
{pattern: 'test/karma-test-shim.js', included: true, watched: true},
|
||||
|
||||
Reference in New Issue
Block a user