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:
parent
efa79b10ae
commit
4bfcb4ed76
@ -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}
|
@ -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}
|
@ -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;
|
||||
|
||||
|
@ -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'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
35
packages/svelte/tests/iconify/10-style-mode.test.ts
Normal file
35
packages/svelte/tests/iconify/10-style-mode.test.ts
Normal 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("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"); width: 1em; height: 1em; display: inline-block; background-color: currentColor; mask-image: var(--svg); mask-repeat: no-repeat; mask-size: 100% 100%;\"></span>"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user