mirror of
https://github.com/iconify/iconify.git
synced 2025-01-07 15:44:05 +00:00
svelte: add rendering modes
This commit is contained in:
parent
efa79b10ae
commit
4bfcb4ed76
@ -107,8 +107,12 @@ export {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data !== null}
|
{#if data}
|
||||||
<svg {...data.attributes}>
|
{#if data.svg}
|
||||||
{@html data.body}
|
<svg {...data.attributes}>
|
||||||
</svg>
|
{@html data.body}
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<span {...data.attributes} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
@ -24,8 +24,12 @@ export {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data !== null}
|
{#if data}
|
||||||
<svg {...data.attributes}>
|
{#if data.svg}
|
||||||
{@html data.body}
|
<svg {...data.attributes}>
|
||||||
</svg>
|
{@html data.body}
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<span {...data.attributes} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
@ -3,6 +3,16 @@ import type { IconifyIconCustomisations as RawIconCustomisations } from '@iconif
|
|||||||
|
|
||||||
export { RawIconCustomisations };
|
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
|
// Allow rotation to be string
|
||||||
/**
|
/**
|
||||||
* Icon customisations
|
* Icon customisations
|
||||||
@ -18,6 +28,9 @@ export interface IconifyIconProps extends IconifyIconCustomisations {
|
|||||||
// Icon object
|
// Icon object
|
||||||
icon: IconifyIcon | string;
|
icon: IconifyIcon | string;
|
||||||
|
|
||||||
|
// Render mode
|
||||||
|
mode?: IconifyRenderMode;
|
||||||
|
|
||||||
// Style
|
// Style
|
||||||
color?: string;
|
color?: string;
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ import {
|
|||||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||||
import { replaceIDs } from '@iconify/utils/lib/svg/id';
|
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
|
* Default SVG attributes
|
||||||
@ -22,13 +24,53 @@ const svgDefaults = {
|
|||||||
'role': 'img',
|
'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
|
* Result
|
||||||
*/
|
*/
|
||||||
export interface RenderResult {
|
interface RenderSVGResult {
|
||||||
|
svg: true;
|
||||||
attributes: Record<string, unknown>;
|
attributes: Record<string, unknown>;
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
interface RenderSPANResult {
|
||||||
|
svg: false;
|
||||||
|
attributes: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
export type RenderResult = RenderSVGResult | RenderSPANResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate icon from properties
|
* Generate icon from properties
|
||||||
@ -43,7 +85,12 @@ export function render(
|
|||||||
defaults,
|
defaults,
|
||||||
props as typeof 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
|
// Create style if missing
|
||||||
let style = typeof props.style === 'string' ? props.style : '';
|
let style = typeof props.style === 'string' ? props.style : '';
|
||||||
@ -59,6 +106,7 @@ export function render(
|
|||||||
case 'icon':
|
case 'icon':
|
||||||
case 'style':
|
case 'style':
|
||||||
case 'onLoad':
|
case 'onLoad':
|
||||||
|
case 'mode':
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Boolean attributes
|
// Boolean attributes
|
||||||
@ -126,37 +174,72 @@ export function render(
|
|||||||
|
|
||||||
// Generate icon
|
// Generate icon
|
||||||
const item = iconToSVG(icon, customisations);
|
const item = iconToSVG(icon, customisations);
|
||||||
|
const renderAttribs = item.attributes;
|
||||||
|
|
||||||
// Add icon stuff
|
// Inline mode
|
||||||
for (let key in item.attributes) {
|
|
||||||
componentProps[key] =
|
|
||||||
item.attributes[key as keyof typeof item.attributes];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.inline) {
|
if (item.inline) {
|
||||||
// Style overrides it
|
// Style overrides it
|
||||||
style = 'vertical-align: -0.125em; ' + style;
|
style = 'vertical-align: -0.125em; ' + style;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style
|
if (mode === 'inline') {
|
||||||
if (style !== '') {
|
// Add icon stuff
|
||||||
componentProps.style = style;
|
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
|
const { body, width, height } = icon;
|
||||||
let localCounter = 0;
|
const useMask =
|
||||||
let id = props.id;
|
mode === 'mask' ||
|
||||||
if (typeof id === 'string') {
|
(mode === 'bg' ? false : body.indexOf('currentColor') !== -1);
|
||||||
// Convert '-' to '_' to avoid errors in animations
|
|
||||||
id = id.replace(/-/g, '_');
|
// 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 {
|
return {
|
||||||
|
svg: false,
|
||||||
attributes: componentProps,
|
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