2
0
mirror of https://github.com/iconify/iconify.git synced 2025-01-06 07:20:40 +00:00

Add class names to icons loaded from API in components, matching SVG framework functionality

This commit is contained in:
Vjacheslav Trushkin 2021-05-13 15:46:50 +03:00
parent 144604ce2b
commit 3f6265fdac
14 changed files with 313 additions and 85 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import type { IconifyJSON } from '@iconify/types';
// Core
import type { IconifyIconName } from '@iconify/core/lib/icon/name';
import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import type {
IconifyIconSize,
IconifyHorizontalIconAlignment,
@ -310,10 +310,13 @@ interface InternalIconProps extends IconProps {
_inline: boolean;
}
type IconComponentData = Required<IconifyIcon> | null;
interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
interface IconComponentState {
data: IconComponentData;
icon: IconComponentData | null;
}
interface ComponentAbortData {
@ -332,7 +335,7 @@ class IconComponent extends React.Component<
super(props);
this.state = {
// Render placeholder before component is mounted
data: null,
icon: null,
};
}
@ -349,10 +352,10 @@ class IconComponent extends React.Component<
/**
* Update state
*/
_setData(data: IconComponentData) {
if (this.state.data !== data) {
_setData(icon: IconComponentData | null) {
if (this.state.icon !== icon) {
this.setState({
data,
icon,
});
}
}
@ -374,22 +377,28 @@ class IconComponent extends React.Component<
this._icon = '';
this._abortLoading();
if (changed || state.data === null) {
if (changed || state.icon === null) {
// Set data if it was changed
this._setData(fullIcon(icon));
this._setData({
data: fullIcon(icon),
});
}
return;
}
// Invalid icon?
if (typeof icon !== 'string') {
let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this._abortLoading();
this._setData(null);
return;
}
// Load icon
const data = getIconData(icon);
const data = getIconData(iconName);
if (data === null) {
// Icon needs to be loaded
if (!this._loading || this._loading.name !== icon) {
@ -400,7 +409,7 @@ class IconComponent extends React.Component<
this._loading = {
name: icon,
abort: API.loadIcons(
[icon],
[iconName],
this._checkIcon.bind(this, false)
),
};
@ -409,11 +418,25 @@ class IconComponent extends React.Component<
}
// Icon data is available
if (this._icon !== icon || state.data === null) {
if (this._icon !== icon || state.icon === null) {
// New icon or icon has been loaded
this._abortLoading();
this._icon = icon;
this._setData(data);
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
// Set data
this._setData({
data,
classes,
});
if (this.props.onLoad) {
this.props.onLoad(icon);
}
@ -448,17 +471,28 @@ class IconComponent extends React.Component<
*/
render() {
const props = this.props;
const data = this.state.data;
const icon = this.state.icon;
if (data === null) {
if (icon === null) {
// Render placeholder
return props.children
? (props.children as JSX.Element)
: React.createElement('span', {});
}
// Add classes
let newProps = props;
if (icon.classes) {
newProps = merge(props, {
className:
(typeof props.className === 'string'
? props.className + ' '
: '') + icon.classes.join(' '),
} as typeof props);
}
// Render icon
return render(data, props, props._inline, props._ref);
return render(icon.data, newProps, props._inline, props._ref);
}
}

View File

@ -15,6 +15,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -73,6 +74,7 @@ describe('Rendering icon', () => {
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
className,
},
children: null,
});
@ -88,6 +90,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -137,6 +140,7 @@ describe('Rendering icon', () => {
iconData.width +
' ' +
iconData.height,
'className': 'test ' + className,
},
children: null,
});
@ -157,6 +161,7 @@ describe('Rendering icon', () => {
const component = renderer.create(
<Icon
icon={iconName}
className="test"
onLoad={(name) => {
expect(name).toEqual(iconName);
expect(onLoadCalled).toEqual(false);

View File

@ -23,6 +23,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = (name) => {
@ -91,6 +92,7 @@ describe('Rendering icon', () => {
iconData.width +
' ' +
iconData.height,
className,
},
children: null,
});
@ -151,6 +153,7 @@ describe('Rendering icon', () => {
iconData2.width +
' ' +
iconData2.height,
className,
},
children: null,
});
@ -190,6 +193,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true;
mockAPIData({
@ -257,6 +261,7 @@ describe('Rendering icon', () => {
iconData2.width +
' ' +
iconData2.height,
className,
},
children: null,
});
@ -292,6 +297,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({
provider,
@ -336,6 +342,7 @@ describe('Rendering icon', () => {
iconData.width +
' ' +
iconData.height,
className,
},
children: null,
});
@ -371,6 +378,7 @@ describe('Rendering icon', () => {
iconData.width +
' ' +
iconData.height,
className,
},
children: null,
});

View File

@ -5,8 +5,7 @@ import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
@ -16,6 +15,7 @@ describe('Testing references', () => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
mockAPIData({
provider,
prefix,

View File

@ -26,7 +26,12 @@
// Generate data
$: {
counter;
data = mounted ? generateIcon(checkIconState($$props.icon, state, loaded, $$props.onLoad), $$props) : null;
const iconData = checkIconState($$props.icon, state, loaded, $$props.onLoad);
data = mounted && iconData ? generateIcon(iconData.data, $$props) : null;
if (data && iconData.classes) {
// Add classes
data.attributes['class'] = (typeof $$props['class'] === 'string' ? $$props['class'] + ' ' : '') + iconData.classes.join(' ');
}
}
// Increase counter when loaded to force re-calculation of data

View File

@ -1,7 +1,7 @@
import type { IconifyJSON } from '@iconify/types';
// Core
import type { IconifyIconName } from '@iconify/core/lib/icon/name';
import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import type {
IconifyIconSize,
IconifyHorizontalIconAlignment,
@ -326,6 +326,14 @@ type IconStateCallback = () => void;
*/
export type IconifyIconOnLoad = (name: string) => void;
/**
* checkIconState result
*/
export interface CheckIconStateResult {
data: IconComponentData;
classes?: string[];
}
/**
* Check if component needs to be updated
*/
@ -334,7 +342,7 @@ export function checkIconState(
state: IconState,
callback: IconStateCallback,
onload?: IconifyIconOnLoad
): IconComponentData {
): CheckIconStateResult | null {
// Abort loading icon
function abortLoading() {
if (state.loading) {
@ -352,17 +360,21 @@ export function checkIconState(
// Stop loading
state.name = '';
abortLoading();
return fullIcon(icon);
return { data: fullIcon(icon) };
}
// Invalid icon
if (typeof icon !== 'string') {
// Invalid icon?
let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
abortLoading();
return null;
}
// Load icon
const data = getIconData(icon);
const data = getIconData(iconName);
if (data === null) {
// Icon needs to be loaded
if (!state.loading || state.loading.name !== icon) {
@ -371,7 +383,7 @@ export function checkIconState(
state.name = '';
state.loading = {
name: icon,
abort: API.loadIcons([icon], callback),
abort: API.loadIcons([iconName], callback),
};
}
return null;
@ -385,7 +397,17 @@ export function checkIconState(
onload(icon);
}
}
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
}
/**

View File

@ -4,8 +4,7 @@ import { mockAPIData } from '@iconify/core/lib/api/modules/mock';
import { provider, nextPrefix } from './load';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
@ -15,6 +14,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -59,7 +59,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// Make sure onLoad has been called
@ -73,6 +75,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -106,7 +109,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="test ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -124,6 +129,8 @@ describe('Rendering icon', () => {
// Render component
const component = render(Icon, {
icon: iconName,
// Also testing simple class
class: 'test',
onLoad: (name) => {
expect(name).toEqual(iconName);
expect(onLoadCalled).toEqual(false);

View File

@ -6,15 +6,13 @@ import ChangeIcon from './fixtures/ChangeIcon.svelte';
import ChangeProps from './fixtures/ChangeProps.svelte';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
const iconData2 = {
body:
'<path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"/>',
body: '<path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"/>',
width: 32,
height: 32,
};
@ -26,6 +24,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call
let triggerSwap;
@ -63,7 +62,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -107,7 +108,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
// onLoad should have been called for second icon
@ -163,6 +166,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call
let isSync = true;
let triggerSwap;
@ -219,7 +223,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
// onLoad should have been called for second icon
@ -269,6 +275,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
let triggerSwap;
@ -303,7 +310,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -316,13 +325,14 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
// Check HTML again
const node = component.container.querySelector(
'svg'
);
const node =
component.container.querySelector('svg');
const html = node.parentNode.innerHTML;
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
);
done();

View File

@ -11,7 +11,7 @@ import {
import { IconifyJSON } from '@iconify/types';
// Core
import { IconifyIconName } from '@iconify/core/lib/icon/name';
import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import {
IconifyIconSize,
IconifyHorizontalIconAlignment,
@ -27,8 +27,9 @@ import {
IconifyBuilderFunctions,
builderFunctions,
} from '@iconify/core/lib/builder/functions';
import type { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
import { merge } from '@iconify/core/lib/misc/merge';
// Modules
import { coreModules } from '@iconify/core/lib/modules';
@ -312,6 +313,11 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
/**
* Component
*/
interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
export const Icon = defineComponent({
// Do not inherit other attributes: it is handled by render()
inheritAttrs: false,
@ -350,7 +356,10 @@ export const Icon = defineComponent({
}
},
// Get data for icon to render or null
getIcon(icon: IconifyIcon | string, onload?: IconifyIconOnLoad) {
getIcon(
icon: IconifyIcon | string,
onload?: IconifyIconOnLoad
): IconComponentData | null {
// Icon is an object
if (
typeof icon === 'object' &&
@ -360,17 +369,23 @@ export const Icon = defineComponent({
// Stop loading
this._name = '';
this.abortLoading();
return fullIcon(icon);
return {
data: fullIcon(icon),
};
}
// Invalid icon?
if (typeof icon !== 'string') {
let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this.abortLoading();
return null;
}
// Load icon
const data = getIconData(icon);
const data = getIconData(iconName);
if (data === null) {
// Icon needs to be loaded
if (!this._loadingIcon || this._loadingIcon.name !== icon) {
@ -379,7 +394,7 @@ export const Icon = defineComponent({
this._name = '';
this._loadingIcon = {
name: icon,
abort: API.loadIcons([icon], () => {
abort: API.loadIcons([iconName], () => {
this.counter++;
}),
};
@ -395,7 +410,17 @@ export const Icon = defineComponent({
onload(icon);
}
}
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
},
},
@ -410,14 +435,28 @@ export const Icon = defineComponent({
// Get icon data
const props = this.$attrs;
const icon = this.getIcon(props.icon, props.onLoad);
const icon: IconComponentData | null = this.getIcon(
props.icon,
props.onLoad
);
// Validate icon object
if (!icon) {
return this.$slots.default ? this.$slots.default() : null;
}
// Valid icon: render it
return render(icon, props);
// Add classes
let newProps = props;
if (icon.classes) {
newProps = merge(props, {
class:
(typeof props['class'] === 'string'
? props['class'] + ' '
: '') + icon.classes.join(' '),
});
}
// Render icon
return render(icon.data, newProps);
},
});

View File

@ -15,6 +15,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -48,7 +49,8 @@ describe('Rendering icon', () => {
// Render component
const Wrapper = {
components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`,
// Also test class string
template: `<Icon icon="${iconName}" :onLoad="onLoad" class="test" />`,
methods: {
onLoad(name) {
expect(name).toEqual(iconName);
@ -62,7 +64,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="test ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// Make sure onLoad has been called
@ -76,6 +80,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -106,7 +111,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
// Check HTML
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="foo ' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -124,7 +131,7 @@ describe('Rendering icon', () => {
// Render component
const Wrapper = {
components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`,
template: `<Icon icon="${iconName}" :onLoad="onLoad" :class="testClass" />`,
methods: {
onLoad(name) {
expect(name).toEqual(iconName);
@ -132,6 +139,15 @@ describe('Rendering icon', () => {
onLoadCalled = true;
},
},
data() {
// Test dynamic class
return {
testClass: {
foo: true,
bar: false,
},
};
},
};
const wrapper = mount(Wrapper, {});

View File

@ -24,6 +24,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = name => {
@ -72,7 +73,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -113,7 +116,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
// onLoad should have been called for second icon
@ -151,6 +156,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true;
mockAPIData({
@ -198,7 +204,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
done();
@ -234,6 +242,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({
provider,
@ -259,7 +268,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// Add horizontal flip and style
@ -276,7 +287,9 @@ describe('Rendering icon', () => {
expect(
wrapper.html().replace(/\s*\n\s*/g, '')
).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="' +
className +
'" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
);
done();

View File

@ -3,7 +3,7 @@ import { ExtendedVue } from 'vue/types/vue';
import { IconifyJSON } from '@iconify/types';
// Core
import { IconifyIconName } from '@iconify/core/lib/icon/name';
import { IconifyIconName, stringToIcon } from '@iconify/core/lib/icon/name';
import {
IconifyIconSize,
IconifyHorizontalIconAlignment,
@ -19,8 +19,9 @@ import {
IconifyBuilderFunctions,
builderFunctions,
} from '@iconify/core/lib/builder/functions';
import type { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { IconifyIconBuildResult } from '@iconify/core/lib/builder';
import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
import { merge } from '@iconify/core/lib/misc/merge';
// Modules
import { coreModules } from '@iconify/core/lib/modules';
@ -304,6 +305,11 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
/**
* Component
*/
interface IconComponentData {
data: Required<IconifyIcon>;
classes?: string[];
}
export const Icon = Vue.extend({
// Do not inherit other attributes: it is handled by render()
// In Vue 2 style is still passed!
@ -340,7 +346,10 @@ export const Icon = Vue.extend({
}
},
// Get data for icon to render or null
getIcon(icon: IconifyIcon | string, onload?: IconifyIconOnLoad) {
getIcon(
icon: IconifyIcon | string,
onload?: IconifyIconOnLoad
): IconComponentData | null {
// Icon is an object
if (
typeof icon === 'object' &&
@ -350,17 +359,23 @@ export const Icon = Vue.extend({
// Stop loading
this._name = '';
this.abortLoading();
return fullIcon(icon);
return {
data: fullIcon(icon),
};
}
// Invalid icon?
if (typeof icon !== 'string') {
let iconName: IconifyIconName | null;
if (
typeof icon !== 'string' ||
(iconName = stringToIcon(icon, false, true)) === null
) {
this.abortLoading();
return null;
}
// Load icon
const data = getIconData(icon);
const data = getIconData(iconName);
if (data === null) {
// Icon needs to be loaded
if (!this._loadingIcon || this._loadingIcon.name !== icon) {
@ -369,7 +384,7 @@ export const Icon = Vue.extend({
this._name = '';
this._loadingIcon = {
name: icon,
abort: API.loadIcons([icon], () => {
abort: API.loadIcons([iconName], () => {
this.$forceUpdate();
}),
};
@ -385,7 +400,17 @@ export const Icon = Vue.extend({
onload(icon);
}
}
return data;
// Add classes
const classes: string[] = ['iconify'];
if (iconName.prefix !== '') {
classes.push('iconify--' + iconName.prefix);
}
if (iconName.provider !== '') {
classes.push('iconify--' + iconName.provider);
}
return { data, classes };
},
},
@ -410,7 +435,10 @@ export const Icon = Vue.extend({
// Get icon data
const props = this.$attrs;
const icon = this.getIcon(props.icon, props.onLoad);
const icon: IconComponentData | null = this.getIcon(
props.icon,
props.onLoad
);
// Validate icon object
if (!icon) {
@ -418,7 +446,18 @@ export const Icon = Vue.extend({
return placeholder(this.$slots);
}
// Valid icon: render it
return render(createElement, props, this.$data, icon);
// Add classes
let context = this.$data;
if (icon.classes) {
context = merge(context, {
class:
(typeof context['class'] === 'string'
? context['class'] + ' '
: '') + icon.classes.join(' '),
});
}
// Render icon
return render(createElement, props, context, icon.data);
},
});

View File

@ -14,6 +14,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'render-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -47,7 +48,8 @@ describe('Rendering icon', () => {
// Render component
const Wrapper = {
components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`,
// Also test class string
template: `<Icon icon="${iconName}" :onLoad="onLoad" class="test" />`,
methods: {
onLoad(name) {
expect(name).toEqual(iconName);
@ -61,7 +63,9 @@ describe('Rendering icon', () => {
// Check HTML
expect(html).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="test ' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// Make sure onLoad has been called
@ -75,6 +79,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'mock-test';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = false;
mockAPIData({
@ -105,7 +110,10 @@ describe('Rendering icon', () => {
setTimeout(() => {
// Check HTML
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
// 'foo' is appended because of weird Vue 2 behavior. Fixed in Vue 3
' foo"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -123,7 +131,7 @@ describe('Rendering icon', () => {
// Render component
const Wrapper = {
components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`,
template: `<Icon icon="${iconName}" :onLoad="onLoad" :class="testClass" />`,
methods: {
onLoad(name) {
expect(name).toEqual(iconName);
@ -131,6 +139,15 @@ describe('Rendering icon', () => {
onLoadCalled = true;
},
},
data() {
// Test dynamic class
return {
testClass: {
foo: true,
bar: false,
},
};
},
};
const wrapper = mount(Wrapper, {});
@ -177,7 +194,7 @@ describe('Rendering icon', () => {
// Render component
const Wrapper = {
components: { Icon },
template: `<Icon icon="${iconName}" :onLoad='onLoad' />`,
template: `<Icon icon="${iconName}" :onLoad="onLoad" />`,
methods: {
onLoad() {
throw new Error('onLoad called for empty icon!');

View File

@ -22,6 +22,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let onLoadCalled = ''; // Name of icon from last onLoad call
const onLoad = (name) => {
@ -70,7 +71,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// onLoad should have been called
@ -111,7 +114,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
// onLoad should have been called for second icon
@ -147,6 +152,7 @@ describe('Rendering icon', () => {
const name2 = 'changing-prop2';
const iconName = `@${provider}:${prefix}:${name}`;
const iconName2 = `@${provider}:${prefix}:${name2}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
let isSync = true;
mockAPIData({
@ -194,7 +200,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="' +
className +
'"><path d="M19.031 4.281l-11 11l-.687.719l.687.719l11 11l1.438-1.438L10.187 16L20.47 5.719z" fill="currentColor"></path></svg>'
);
done();
@ -229,6 +237,7 @@ describe('Rendering icon', () => {
const prefix = nextPrefix();
const name = 'multiple-props';
const iconName = `@${provider}:${prefix}:${name}`;
const className = `iconify iconify--${prefix} iconify--${provider}`;
mockAPIData({
provider,
@ -254,7 +263,9 @@ describe('Rendering icon', () => {
setTimeout(() => {
setTimeout(() => {
expect(wrapper.html().replace(/\s*\n\s*/g, '')).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></svg>'
);
// Add horizontal flip and style
@ -271,7 +282,9 @@ describe('Rendering icon', () => {
expect(
wrapper.html().replace(/\s*\n\s*/g, '')
).toEqual(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="' +
className +
'" style="color: red;"><g transform="translate(24 0) scale(-1 1)"><path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"></path></g></svg>'
);
done();