From 69b6290f7b4601f88c2c8365407d1aa5db45e27d Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 3 Nov 2024 22:28:37 +0200 Subject: [PATCH] feat: add custom loader to svelte component, also add ssr prop to match vue component --- components/svelte/src/Icon.svelte | 7 +++- components/svelte/src/functions.ts | 18 ++++++++- components/svelte/src/iconify.ts | 11 ++++- components/svelte/src/props.ts | 3 ++ components/svelte/src/render.ts | 1 + .../svelte/tests/iconify/10-basic.test.ts | 40 ++++++++++++++++++- 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/components/svelte/src/Icon.svelte b/components/svelte/src/Icon.svelte index ed832a5..c1c40cb 100644 --- a/components/svelte/src/Icon.svelte +++ b/components/svelte/src/Icon.svelte @@ -16,6 +16,8 @@ import { buildIcon, loadIcons, loadIcon, + setCustomIconLoader, + setCustomIconsLoader, addAPIProvider, _api } from './functions'; @@ -34,6 +36,8 @@ export { buildIcon, loadIcons, loadIcon, + setCustomIconLoader, + setCustomIconsLoader, addAPIProvider, _api } @@ -79,7 +83,8 @@ export { // Generate data $: { counter; - const iconData = checkIconState($$props.icon, state, mounted, loaded, onLoad); + const isMounted = !!$$props.ssr || mounted; + const iconData = checkIconState($$props.icon, state, isMounted, loaded, onLoad); data = iconData ? generateIcon(iconData.data, $$props) : null; if (data && iconData.classes) { // Add classes diff --git a/components/svelte/src/functions.ts b/components/svelte/src/functions.ts index 93bc749..c58b405 100644 --- a/components/svelte/src/functions.ts +++ b/components/svelte/src/functions.ts @@ -22,6 +22,10 @@ import type { IconifyIconBuildResult } from '@iconify/utils/lib/svg/build'; import { defaultIconProps } from '@iconify/utils/lib/icon/defaults'; // API +import type { + IconifyCustomIconLoader, + IconifyCustomIconsLoader, +} from '@iconify/core/lib/api/types'; import type { IconifyAPIFunctions, IconifyAPIInternalFunctions, @@ -54,6 +58,10 @@ import type { IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; +import { + setCustomIconLoader, + setCustomIconsLoader, +} from '@iconify/core/lib/api/loaders'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; // Cache @@ -112,6 +120,8 @@ export { PartialIconifyAPIConfig, IconifyAPIQueryParams, IconifyAPICustomQueryParams, + IconifyCustomIconLoader, + IconifyCustomIconsLoader, }; // Builder functions @@ -360,7 +370,13 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons, loadIcon }; +export { + addAPIProvider, + loadIcons, + loadIcon, + setCustomIconLoader, + setCustomIconsLoader, +}; // IconifyStorageFunctions export { diff --git a/components/svelte/src/iconify.ts b/components/svelte/src/iconify.ts index 3c836ef..b891190 100644 --- a/components/svelte/src/iconify.ts +++ b/components/svelte/src/iconify.ts @@ -34,6 +34,8 @@ export { PartialIconifyAPIConfig, IconifyAPIQueryParams, IconifyAPICustomQueryParams, + IconifyCustomIconLoader, + IconifyCustomIconsLoader, } from './functions'; // Builder functions @@ -60,4 +62,11 @@ export { export { calculateSize, replaceIDs, buildIcon } from './functions'; -export { loadIcons, loadIcon, addAPIProvider, _api } from './functions'; +export { + addAPIProvider, + loadIcons, + loadIcon, + setCustomIconLoader, + setCustomIconsLoader, + _api, +} from './functions'; diff --git a/components/svelte/src/props.ts b/components/svelte/src/props.ts index 53182d7..8d03515 100644 --- a/components/svelte/src/props.ts +++ b/components/svelte/src/props.ts @@ -21,6 +21,9 @@ export type IconifyIconCustomisations = RawIconifyIconCustomisations & { // Inline mode inline?: boolean; + + // SSR: render icon instantly without waiting for component to mount + ssr?: boolean; }; export const defaultExtendedIconCustomisations = { diff --git a/components/svelte/src/render.ts b/components/svelte/src/render.ts index efa7f8c..c759fad 100644 --- a/components/svelte/src/render.ts +++ b/components/svelte/src/render.ts @@ -112,6 +112,7 @@ export function render( case 'style': case 'onLoad': case 'mode': + case 'ssr': break; // Boolean attributes diff --git a/components/svelte/tests/iconify/10-basic.test.ts b/components/svelte/tests/iconify/10-basic.test.ts index 697c2c3..3bc36b8 100644 --- a/components/svelte/tests/iconify/10-basic.test.ts +++ b/components/svelte/tests/iconify/10-basic.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'vitest'; import { render } from '@testing-library/svelte'; -import Icon from '../../'; +import Icon, { setCustomIconLoader, loadIcon } from '../../'; const iconData = { body: '', @@ -35,4 +35,42 @@ describe('Creating component', () => { const style = node.style; expect(typeof style).toBe('object'); }); + + test('custom loader', async () => { + const prefix = 'customLoader'; + const name = 'TestIcon'; + + // Set custom loader and load icon data + setCustomIconLoader(() => { + return iconData; + }, prefix); + await loadIcon(`${prefix}:${name}`); + + // Create component + const component = render(Icon, { + 'icon': `${prefix}:${name}`, + 'ssr': true, + 'on:load': () => { + // Should be called only for icons loaded from API + throw new Error('onLoad called for object!'); + }, + }); + const node = component.container.querySelector('svg')!; + expect(node).not.toBeNull(); + expect(node.parentNode).not.toBeNull(); + const html = (node.parentNode as HTMLDivElement).innerHTML; + + // Check HTML + expect(html.replace(//gm, '')).toBe( + `` + ); + + // Make sure getAttribute() works, used in other tests + expect(node.getAttribute('xmlns')).toBe('http://www.w3.org/2000/svg'); + expect(node.getAttribute('aria-hidden')).toBe('true'); + + // Make sure style exists + const style = node.style; + expect(typeof style).toBe('object'); + }); });