diff --git a/packages/svelte/src/Icon.svelte b/packages/svelte/src/Icon.svelte
index ddcbfc7..15af2c9 100644
--- a/packages/svelte/src/Icon.svelte
+++ b/packages/svelte/src/Icon.svelte
@@ -107,8 +107,12 @@ export {
})
-{#if data !== null}
-
+{#if data}
+ {#if data.svg}
+
+ {:else}
+
+ {/if}
{/if}
\ No newline at end of file
diff --git a/packages/svelte/src/OfflineIcon.svelte b/packages/svelte/src/OfflineIcon.svelte
index 0cf82f9..9bb5fb6 100644
--- a/packages/svelte/src/OfflineIcon.svelte
+++ b/packages/svelte/src/OfflineIcon.svelte
@@ -24,8 +24,12 @@ export {
}
-{#if data !== null}
-
+{#if data}
+ {#if data.svg}
+
+ {:else}
+
+ {/if}
{/if}
\ No newline at end of file
diff --git a/packages/svelte/src/props.ts b/packages/svelte/src/props.ts
index 7180f93..f64fe2b 100644
--- a/packages/svelte/src/props.ts
+++ b/packages/svelte/src/props.ts
@@ -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;
diff --git a/packages/svelte/src/render.ts b/packages/svelte/src/render.ts
index 5f1e163..fa7f4ec 100644
--- a/packages/svelte/src/render.ts
+++ b/packages/svelte/src/render.ts
@@ -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 = {
+ display: 'inline-block',
+};
+
+const monotoneProps: Record = {
+ 'background-color': 'currentColor',
+};
+
+const coloredProps: Record = {
+ 'background-color': 'transparent',
+};
+
+// Dynamically add common props to variables above
+const propsToAdd: Record = {
+ image: 'var(--svg)',
+ repeat: 'no-repeat',
+ size: '100% 100%',
+};
+const propsToAddTo: Record> = {
+ '-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;
body: string;
}
+interface RenderSPANResult {
+ svg: false;
+ attributes: Record;
+}
+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;
+
+ // Check mode
+ const mode: IconifyRenderMode = props.mode || 'inline';
+ const componentProps = (
+ mode === 'inline' ? { ...svgDefaults } : {}
+ ) as Record;
// 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 = {
+ '--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'
- ),
};
}
diff --git a/packages/svelte/tests/iconify/10-style-mode.test.ts b/packages/svelte/tests/iconify/10-style-mode.test.ts
new file mode 100644
index 0000000..0ffd720
--- /dev/null
+++ b/packages/svelte/tests/iconify/10-style-mode.test.ts
@@ -0,0 +1,35 @@
+/**
+ * @jest-environment jsdom
+ */
+import { render } from '@testing-library/svelte';
+import Icon from '../../dist';
+
+const iconData = {
+ body: '',
+ 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(
+ ""
+ );
+ });
+});