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

svelte: add rendering modes

This commit is contained in:
Vjacheslav Trushkin 2022-04-12 11:09:22 +03:00
parent efa79b10ae
commit 4bfcb4ed76
5 changed files with 170 additions and 31 deletions

View File

@ -107,8 +107,12 @@ export {
})
</script>
{#if data !== null}
<svg {...data.attributes}>
{@html data.body}
</svg>
{#if data}
{#if data.svg}
<svg {...data.attributes}>
{@html data.body}
</svg>
{:else}
<span {...data.attributes} />
{/if}
{/if}

View File

@ -24,8 +24,12 @@ export {
}
</script>
{#if data !== null}
<svg {...data.attributes}>
{@html data.body}
</svg>
{#if data}
{#if data.svg}
<svg {...data.attributes}>
{@html data.body}
</svg>
{:else}
<span {...data.attributes} />
{/if}
{/if}

View File

@ -3,6 +3,16 @@ import type { IconifyIconCustomisations as RawIconCustomisations } from '@iconif
export { RawIconCustomisations };
/**
* Icon render mode
*
* 'style' = 'bg' or 'mask', depending on icon content
* 'bg' = inline style using `background`
* 'mask' = inline style using `mask`
* 'inline' = inline SVG.
*/
export type IconifyRenderMode = 'style' | 'bg' | 'mask' | 'inline';
// Allow rotation to be string
/**
* Icon customisations
@ -18,6 +28,9 @@ export interface IconifyIconProps extends IconifyIconCustomisations {
// Icon object
icon: IconifyIcon | string;
// Render mode
mode?: IconifyRenderMode;
// Style
color?: string;

View File

@ -10,7 +10,9 @@ import {
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
import { iconToSVG } from '@iconify/utils/lib/svg/build';
import { replaceIDs } from '@iconify/utils/lib/svg/id';
import type { IconProps } from './props';
import { iconToHTML } from '@iconify/utils/lib/svg/html';
import { svgToURL } from '@iconify/utils/lib/svg/url';
import type { IconProps, IconifyRenderMode } from './props';
/**
* Default SVG attributes
@ -22,13 +24,53 @@ const svgDefaults = {
'role': 'img',
};
/**
* Style modes
*/
const commonProps: Record<string, string> = {
display: 'inline-block',
};
const monotoneProps: Record<string, string> = {
'background-color': 'currentColor',
};
const coloredProps: Record<string, string> = {
'background-color': 'transparent',
};
// Dynamically add common props to variables above
const propsToAdd: Record<string, string> = {
image: 'var(--svg)',
repeat: 'no-repeat',
size: '100% 100%',
};
const propsToAddTo: Record<string, Record<string, string>> = {
'-webkit-mask': monotoneProps,
'mask': monotoneProps,
'background': coloredProps,
};
for (const prefix in propsToAddTo) {
const list = propsToAddTo[prefix];
for (const prop in propsToAdd) {
list[prefix + '-' + prop] = propsToAdd[prop];
}
}
/**
* Result
*/
export interface RenderResult {
interface RenderSVGResult {
svg: true;
attributes: Record<string, unknown>;
body: string;
}
interface RenderSPANResult {
svg: false;
attributes: Record<string, unknown>;
}
export type RenderResult = RenderSVGResult | RenderSPANResult;
/**
* Generate icon from properties
@ -43,7 +85,12 @@ export function render(
defaults,
props as typeof defaults
);
const componentProps = { ...svgDefaults } as Record<string, unknown>;
// Check mode
const mode: IconifyRenderMode = props.mode || 'inline';
const componentProps = (
mode === 'inline' ? { ...svgDefaults } : {}
) as Record<string, unknown>;
// Create style if missing
let style = typeof props.style === 'string' ? props.style : '';
@ -59,6 +106,7 @@ export function render(
case 'icon':
case 'style':
case 'onLoad':
case 'mode':
break;
// Boolean attributes
@ -126,37 +174,72 @@ export function render(
// Generate icon
const item = iconToSVG(icon, customisations);
const renderAttribs = item.attributes;
// Add icon stuff
for (let key in item.attributes) {
componentProps[key] =
item.attributes[key as keyof typeof item.attributes];
}
// Inline mode
if (item.inline) {
// Style overrides it
style = 'vertical-align: -0.125em; ' + style;
}
// Style
if (style !== '') {
componentProps.style = style;
if (mode === 'inline') {
// Add icon stuff
Object.assign(componentProps, renderAttribs);
// Style
if (style !== '') {
componentProps.style = style;
}
// Counter for ids based on "id" property to render icons consistently on server and client
let localCounter = 0;
let id = props.id;
if (typeof id === 'string') {
// Convert '-' to '_' to avoid errors in animations
id = id.replace(/-/g, '_');
}
// Generate HTML
return {
svg: true,
attributes: componentProps,
body: replaceIDs(
item.body,
id ? () => id + 'ID' + localCounter++ : 'iconifySvelte'
),
};
}
// Counter for ids based on "id" property to render icons consistently on server and client
let localCounter = 0;
let id = props.id;
if (typeof id === 'string') {
// Convert '-' to '_' to avoid errors in animations
id = id.replace(/-/g, '_');
const { body, width, height } = icon;
const useMask =
mode === 'mask' ||
(mode === 'bg' ? false : body.indexOf('currentColor') !== -1);
// Generate SVG
const html = iconToHTML(body, {
...renderAttribs,
width: width + '',
height: height + '',
});
// Generate style
const url = svgToURL(html);
const styles: Record<string, string> = {
'--svg': url,
'width': renderAttribs.width,
'height': renderAttribs.height,
...commonProps,
...(useMask ? monotoneProps : coloredProps),
};
let customStyle = '';
for (const key in styles) {
customStyle += key + ': ' + styles[key] + ';';
}
// Generate HTML
componentProps.style = customStyle + style;
return {
svg: false,
attributes: componentProps,
body: replaceIDs(
item.body,
id ? () => id + 'ID' + localCounter++ : 'iconifySvelte'
),
};
}

View File

@ -0,0 +1,35 @@
/**
* @jest-environment jsdom
*/
import { render } from '@testing-library/svelte';
import Icon from '../../dist';
const iconData = {
body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Rendering as span', () => {
test('basic icon', () => {
const component = render(Icon, {
icon: iconData,
mode: 'style',
onLoad: () => {
// Should be called only for icons loaded from API
throw new Error('onLoad called for object!');
},
});
const node = component.container.querySelector(
'span'
) as HTMLSpanElement;
expect(node).not.toBeNull();
expect(node.parentNode).not.toBeNull();
const html = node.outerHTML;
// Check HTML
expect(html).toBe(
"<span style=\"--svg: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' preserveAspectRatio='xMidYMid meet' viewBox='0 0 24 24'%3E%3Cpath d='M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z' fill='currentColor'/%3E%3C/svg%3E&quot;); width: 1em; height: 1em; display: inline-block; background-color: currentColor; mask-image: var(--svg); mask-repeat: no-repeat; mask-size: 100% 100%;\"></span>"
);
});
});