2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 23:00:56 +00:00

svg framework: add bg and mask modes, test changing modes

This commit is contained in:
Vjacheslav Trushkin 2022-04-11 11:10:34 +03:00
parent b08d1221e3
commit 40dd0aba48
6 changed files with 153 additions and 16 deletions

View File

@ -47,7 +47,8 @@ for (const prefix in propsToAddTo) {
export function renderBackground(
element: IconifyElement,
props: IconifyElementProps,
iconData: FullIconifyIcon
iconData: FullIconifyIcon,
useMask: boolean
): IconifyElement {
// Generate data to render
const renderData = iconToSVG(iconData, {
@ -74,14 +75,13 @@ export function renderBackground(
);
// Update style
const isMonotone = renderData.body.indexOf('currentColor') !== -1;
const url = svgToURL(html);
const newStyles: Record<string, string> = {
'--svg': url,
'width': renderAttribs.width,
'height': renderAttribs.height,
...commonProps,
...(isMonotone ? monotoneProps : coloredProps),
...(useMask ? monotoneProps : coloredProps),
};
if (renderData.inline) {
newStyles['vertical-align'] = '-0.125em';

View File

@ -8,7 +8,7 @@ export function propsChanged(
props1: IconifyElementProps,
props2: IconifyElementProps
): boolean {
if (props1.name !== props2.name) {
if (props1.name !== props2.name || props1.mode !== props2.mode) {
return true;
}

View File

@ -9,8 +9,13 @@ export const inlineClass = 'iconify-inline';
/**
* 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 IconRenderMode = 'style' | 'inline';
export type IconRenderMode = 'style' | 'bg' | 'mask' | 'inline';
/**
* Data used to verify if icon is the same

View File

@ -123,14 +123,24 @@ export function scanDOM(rootNode?: ObservedNode, addTempNode = false): void {
pauseObservingNode(observedNode);
}
if (
element.tagName.toUpperCase() === 'SVG' ||
props.mode !== 'style'
) {
renderInlineSVG(element, props, iconData);
} else {
renderBackground(element, props, iconData);
if (element.tagName.toUpperCase() !== 'SVG') {
// Check for one of style modes
const mode = props.mode;
const isMask: boolean | null =
mode === 'mask' ||
(mode === 'bg'
? false
: mode === 'style'
? iconData.body.indexOf('currentColor') !== -1
: null);
if (typeof isMask === 'boolean') {
renderBackground(element, props, iconData, isMask);
return;
}
}
renderInlineSVG(element, props, iconData);
}
// Find all elements

View File

@ -0,0 +1,117 @@
import { iconExists } from '@iconify/core/lib/storage/functions';
import {
fakeAPI,
nextPrefix,
setupDOM,
waitDOMReady,
resetState,
mockAPIData,
awaitUntil,
nextTick,
} from './helpers';
import { addBodyNode } from '../src/observer/root';
import { scanDOM } from '../src/scanner/index';
import { elementDataProperty } from '../src/scanner/config';
import { initObserver } from '../src/observer';
describe('Changing render modes', () => {
const provider = nextPrefix();
beforeAll(() => {
fakeAPI(provider);
});
afterEach(resetState);
it('Various background modes', async () => {
const prefix = nextPrefix();
const iconName = `@${provider}:${prefix}:home`;
// Add icon with API
expect(iconExists(iconName)).toBe(false);
mockAPIData({
type: 'icons',
provider,
prefix,
response: {
prefix,
icons: {
home: {
body: '<g />',
},
},
},
});
// Setup DOM and wait for it to be ready
setupDOM(
`<span class="iconify" data-icon="${iconName}" data-mode="style"></span>`
);
await waitDOMReady();
// Observe body
addBodyNode();
initObserver(scanDOM);
// Check HTML and data
expect(document.body.innerHTML).toBe(
`<span class="iconify" data-icon="${iconName}" data-mode="style"></span>`
);
// Wait for re-render
const placeholder = document.body.childNodes[0] as HTMLSpanElement;
const style = placeholder.style;
await awaitUntil(() => !!style.getPropertyValue('--svg'));
// Check HTML
expect(document.body.innerHTML).toBe(
`<span class="iconify iconify--${provider} iconify--${prefix}" data-icon="${iconName}" data-mode="style" style="--svg: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Cg /%3E%3C/svg%3E&quot;); width: 1em; height: 1em; display: inline-block; background-color: transparent; background-repeat: no-repeat; background-size: 100% 100%;"></span>`
);
let data = placeholder[elementDataProperty];
expect(data.addedStyles.indexOf('background-image') !== -1).toBe(true);
expect(data.addedStyles.indexOf('mask-image') !== -1).toBe(false);
expect(style.getPropertyValue('mask-image')).toBe('');
// Render as mask
placeholder.setAttribute('data-mode', 'mask');
await awaitUntil(() => !!style.getPropertyValue('mask-image'));
expect(document.body.innerHTML).toBe(
`<span class="iconify iconify--${provider} iconify--${prefix}" data-icon="${iconName}" data-mode="mask" style="--svg: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Cg /%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>`
);
data = placeholder[elementDataProperty];
expect(data.addedStyles.indexOf('background-image') !== -1).toBe(false);
expect(data.addedStyles.indexOf('mask-image') !== -1).toBe(true);
expect(style.getPropertyValue('background-image')).toBe('');
// Re-render as background
placeholder.setAttribute('data-mode', 'bg');
await awaitUntil(() => !style.getPropertyValue('mask-image'));
expect(document.body.innerHTML).toBe(
`<span class="iconify iconify--${provider} iconify--${prefix}" data-icon="${iconName}" data-mode="bg" style="--svg: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Cg /%3E%3C/svg%3E&quot;); width: 1em; height: 1em; display: inline-block; background-color: transparent; background-repeat: no-repeat; background-size: 100% 100%;"></span>`
);
data = placeholder[elementDataProperty];
expect(data.addedStyles.indexOf('background-image') !== -1).toBe(true);
expect(data.addedStyles.indexOf('mask-image') !== -1).toBe(false);
expect(style.getPropertyValue('mask-image')).toBe('');
// Re-render as SVG
placeholder.setAttribute('data-mode', 'inline');
await awaitUntil(() => document.body.childNodes[0] !== placeholder);
expect(document.body.innerHTML).toBe(
`<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" data-mode="inline" style="" class="iconify iconify--${provider} iconify--${prefix}"><g></g></svg>`
);
const svgData = document.body.childNodes[0][elementDataProperty];
expect(svgData.mode).toBe('inline');
// Change to style (should not work!)
placeholder.setAttribute('data-mode', 'bg');
await awaitUntil(() => svgData.mode !== 'bg');
expect(document.body.innerHTML).toBe(
`<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" data-mode="inline" style="" class="iconify iconify--${provider} iconify--${prefix}"><g></g></svg>`
);
});
});

View File

@ -36,10 +36,15 @@ describe('Testing rendering nodes as background', () => {
// Get node and render it
const { node, props } = items[0];
const result = renderBackground(node, props, {
...iconDefaults,
...data,
});
const result = renderBackground(
node,
props,
{
...iconDefaults,
...data,
},
data.body.indexOf('currentColor') !== -1
);
// Make sure node did not change
expect(result).toBe(node);