mirror of
https://github.com/iconify/iconify.git
synced 2024-11-08 14:20:57 +00:00
Add viewBox and preserveAspectRatio properties to web component
This commit is contained in:
parent
11a298690b
commit
95ff16aee9
@ -21,13 +21,13 @@ Iconify Icon web component renders icons.
|
|||||||
Add this line to your page to load IconifyIcon (you can add it to `<head>` section of the page or before `</body>`):
|
Add this line to your page to load IconifyIcon (you can add it to `<head>` section of the page or before `</body>`):
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://code.iconify.design/iconify-icon/0.0.4/iconify-icon.min.js"></script>
|
<script src="https://code.iconify.design/iconify-icon/0.0.5/iconify-icon.min.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@0.0.4/dist/iconify-icon.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@0.0.5/dist/iconify-icon.min.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `iconify-icon` as a dependency and importing it in your project:
|
or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `iconify-icon` as a dependency and importing it in your project:
|
||||||
|
4
iconify-icon/icon/package-lock.json
generated
4
iconify-icon/icon/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "iconify-icon",
|
"name": "iconify-icon",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "iconify-icon",
|
"name": "iconify-icon",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/types": "^1.1.0"
|
"@iconify/types": "^1.1.0"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "iconify-icon",
|
"name": "iconify-icon",
|
||||||
"description": "Icon web component that loads icon data on demand. Over 100,000 icons to choose from",
|
"description": "Icon web component that loads icon data on demand. Over 100,000 icons to choose from",
|
||||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/iconify-icon.cjs",
|
"main": "./dist/iconify-icon.cjs",
|
||||||
"types": "./dist/iconify-icon.d.ts",
|
"types": "./dist/iconify-icon.d.ts",
|
||||||
|
@ -2,16 +2,25 @@ import type { FullIconCustomisations } from '@iconify/utils/lib/customisations';
|
|||||||
import { defaults } from '@iconify/utils/lib/customisations';
|
import { defaults } from '@iconify/utils/lib/customisations';
|
||||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||||
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
|
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
|
||||||
|
import type { IconifyIconSVGAttributes } from './types';
|
||||||
// Remove 'inline' from defaults
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { inline, ...defaultCustomisations } = defaults;
|
|
||||||
export { defaultCustomisations };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customisations that affect rendering
|
* Customisations that affect rendering
|
||||||
*/
|
*/
|
||||||
export type RenderedIconCustomisations = Omit<FullIconCustomisations, 'inline'>;
|
export type RenderedIconCustomisations = Omit<
|
||||||
|
FullIconCustomisations,
|
||||||
|
'inline'
|
||||||
|
> &
|
||||||
|
IconifyIconSVGAttributes;
|
||||||
|
|
||||||
|
// Remove 'inline' from defaults
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { inline, ...defaultCustomisations } = {
|
||||||
|
...defaults,
|
||||||
|
viewBox: '',
|
||||||
|
preserveAspectRatio: '',
|
||||||
|
} as IconifyIconSVGAttributes & FullIconCustomisations;
|
||||||
|
export { defaultCustomisations };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get customisations
|
* Get customisations
|
||||||
@ -34,6 +43,13 @@ export function getCustomisations(node: Element): RenderedIconCustomisations {
|
|||||||
// Flip
|
// Flip
|
||||||
flipFromString(customisations, attr('flip', ''));
|
flipFromString(customisations, attr('flip', ''));
|
||||||
|
|
||||||
|
// SVG attributes
|
||||||
|
customisations.viewBox = attr('viewBox', attr('viewbox', ''));
|
||||||
|
customisations.preserveAspectRatio = attr(
|
||||||
|
'preserveAspectRatio',
|
||||||
|
attr('preserveaspectratio', '')
|
||||||
|
);
|
||||||
|
|
||||||
return customisations;
|
return customisations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
import type { IconifyIcon } from '@iconify/types';
|
import type { IconifyIcon } from '@iconify/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVG attributes that can be overwritten
|
||||||
|
*/
|
||||||
|
export interface IconifyIconSVGAttributes {
|
||||||
|
viewBox: string;
|
||||||
|
preserveAspectRatio: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon render modes
|
* Icon render modes
|
||||||
*
|
*
|
||||||
@ -33,7 +41,8 @@ export type IconifyIconCustomisationProperties = {
|
|||||||
* All properties
|
* All properties
|
||||||
*/
|
*/
|
||||||
export interface IconifyIconProperties
|
export interface IconifyIconProperties
|
||||||
extends IconifyIconCustomisationProperties {
|
extends IconifyIconCustomisationProperties,
|
||||||
|
Partial<IconifyIconSVGAttributes> {
|
||||||
// Icon to render: name, object or serialised object
|
// Icon to render: name, object or serialised object
|
||||||
icon: string | IconifyIcon;
|
icon: string | IconifyIcon;
|
||||||
|
|
||||||
@ -49,8 +58,9 @@ export interface IconifyIconProperties
|
|||||||
*/
|
*/
|
||||||
export interface IconifyIconAttributes
|
export interface IconifyIconAttributes
|
||||||
extends Partial<
|
extends Partial<
|
||||||
Record<keyof Omit<IconifyIconProperties, 'icon' | 'mode'>, string>
|
Record<keyof Omit<IconifyIconProperties, 'icon' | 'mode'>, string>
|
||||||
> {
|
>,
|
||||||
|
Partial<IconifyIconSVGAttributes> {
|
||||||
// Icon to render: name or serialised object
|
// Icon to render: name or serialised object
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
|
@ -1,18 +1,53 @@
|
|||||||
|
import type { IconifyIcon } from '@iconify/types';
|
||||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||||
import type { RenderedState } from '../state';
|
import type { RenderedState } from '../state';
|
||||||
import { renderSPAN } from './span';
|
import { renderSPAN } from './span';
|
||||||
import { renderSVG } from './svg';
|
import { renderSVG } from './svg';
|
||||||
|
|
||||||
|
// viewBox properties order
|
||||||
|
const viewBoxProps: (keyof IconifyIcon)[] = ['left', 'top', 'width', 'height'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render icon
|
* Render icon
|
||||||
*/
|
*/
|
||||||
export function renderIcon(parent: Element | ShadowRoot, state: RenderedState) {
|
export function renderIcon(parent: Element | ShadowRoot, state: RenderedState) {
|
||||||
|
let iconData = state.icon.data;
|
||||||
|
const customisations = state.customisations;
|
||||||
|
|
||||||
|
// Custom viewBox
|
||||||
|
const viewBox = customisations.viewBox;
|
||||||
|
if (viewBox) {
|
||||||
|
const parts = viewBox.split(/\s+/);
|
||||||
|
const customisedViewBox: Partial<IconifyIcon> = {};
|
||||||
|
let failed = false;
|
||||||
|
if (parts.length === 4) {
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const num = parseFloat(parts[i]);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
failed = true;
|
||||||
|
} else {
|
||||||
|
customisedViewBox[viewBoxProps[i] as 'left'] = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failed) {
|
||||||
|
iconData = {
|
||||||
|
...iconData,
|
||||||
|
...customisedViewBox,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render icon
|
// Render icon
|
||||||
const iconData = state.icon.data;
|
|
||||||
const renderData = iconToSVG(iconData, {
|
const renderData = iconToSVG(iconData, {
|
||||||
...state.customisations,
|
...state.customisations,
|
||||||
inline: state.inline,
|
inline: state.inline,
|
||||||
});
|
});
|
||||||
|
if (customisations.preserveAspectRatio) {
|
||||||
|
renderData.attributes['preserveAspectRatio'] =
|
||||||
|
customisations.preserveAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
const mode = state.renderedMode;
|
const mode = state.renderedMode;
|
||||||
let node: Element;
|
let node: Element;
|
||||||
|
@ -66,6 +66,61 @@ describe('Testing customisations', () => {
|
|||||||
});
|
});
|
||||||
expect(haveCustomisationsChanged(test3, test2)).toBe(true);
|
expect(haveCustomisationsChanged(test3, test2)).toBe(true);
|
||||||
expect(haveCustomisationsChanged(test1, test3)).toBe(true);
|
expect(haveCustomisationsChanged(test1, test3)).toBe(true);
|
||||||
|
expect(haveCustomisationsChanged(test3, emptyCustomisations)).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
expect(getInline(testNode)).toBe(false);
|
expect(getInline(testNode)).toBe(false);
|
||||||
|
|
||||||
|
// viewBox
|
||||||
|
node.innerHTML = '<span viewBox="0 0 24 24"></span>';
|
||||||
|
testNode = node.lastChild as HTMLSpanElement;
|
||||||
|
|
||||||
|
const test4 = getCustomisations(testNode);
|
||||||
|
expect(test4).toEqual({
|
||||||
|
...defaultCustomisations,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
});
|
||||||
|
expect(haveCustomisationsChanged(test4, emptyCustomisations)).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
node.innerHTML = '<span viewbox="0 0 32 32"></span>';
|
||||||
|
testNode = node.lastChild as HTMLSpanElement;
|
||||||
|
|
||||||
|
const test5 = getCustomisations(testNode);
|
||||||
|
expect(test5).toEqual({
|
||||||
|
...defaultCustomisations,
|
||||||
|
viewBox: '0 0 32 32',
|
||||||
|
});
|
||||||
|
expect(haveCustomisationsChanged(test5, test4)).toBe(true);
|
||||||
|
expect(haveCustomisationsChanged(test5, emptyCustomisations)).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// preserveAspectRatio
|
||||||
|
node.innerHTML = '<span preserveAspectRatio="xMidYMid meet"></span>';
|
||||||
|
testNode = node.lastChild as HTMLSpanElement;
|
||||||
|
|
||||||
|
const test6 = getCustomisations(testNode);
|
||||||
|
expect(test6).toEqual({
|
||||||
|
...defaultCustomisations,
|
||||||
|
preserveAspectRatio: 'xMidYMid meet',
|
||||||
|
});
|
||||||
|
expect(haveCustomisationsChanged(test6, emptyCustomisations)).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
node.innerHTML = '<span preserveaspectratio="xMidYMin slice"></span>';
|
||||||
|
testNode = node.lastChild as HTMLSpanElement;
|
||||||
|
|
||||||
|
const test7 = getCustomisations(testNode);
|
||||||
|
expect(test7).toEqual({
|
||||||
|
...defaultCustomisations,
|
||||||
|
preserveAspectRatio: 'xMidYMin slice',
|
||||||
|
});
|
||||||
|
expect(haveCustomisationsChanged(test7, emptyCustomisations)).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(haveCustomisationsChanged(test7, test6)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -69,6 +69,65 @@ describe('Testing rendering loaded icon', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('SVG with custom attributes', () => {
|
||||||
|
// Setup DOM
|
||||||
|
const doc = setupDOM('').window.document;
|
||||||
|
|
||||||
|
// Create container node and add style
|
||||||
|
const node = doc.createElement('div');
|
||||||
|
updateStyle(node, false);
|
||||||
|
|
||||||
|
// Render SVG
|
||||||
|
renderIcon(node, {
|
||||||
|
rendered: true,
|
||||||
|
icon: {
|
||||||
|
value: 'whatever',
|
||||||
|
data: {
|
||||||
|
...iconDefaults,
|
||||||
|
body: '<g />',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renderedMode: 'svg',
|
||||||
|
inline: false,
|
||||||
|
customisations: {
|
||||||
|
...defaultCustomisations,
|
||||||
|
viewBox: '0 0 48 24',
|
||||||
|
preserveAspectRatio: 'xMidYMid meet',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test HTML
|
||||||
|
expect(node.innerHTML).toBe(
|
||||||
|
`<style>${expectedBlock}</style><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="1em" viewBox="0 0 48 24" preserveAspectRatio="xMidYMid meet"><g></g></svg>`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Replace icon content
|
||||||
|
renderIcon(node, {
|
||||||
|
rendered: true,
|
||||||
|
icon: {
|
||||||
|
value: 'whatever',
|
||||||
|
data: {
|
||||||
|
...iconDefaults,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
body: '<g><path d="" /></g>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renderedMode: 'svg',
|
||||||
|
inline: false,
|
||||||
|
customisations: {
|
||||||
|
...defaultCustomisations,
|
||||||
|
rotate: 1,
|
||||||
|
height: 'auto',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test HTML
|
||||||
|
expect(node.innerHTML).toBe(
|
||||||
|
`<style>${expectedBlock}</style><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g transform="rotate(90 12 12)"><g><path d=""></path></g></g></svg>`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('Render as SPAN', () => {
|
it('Render as SPAN', () => {
|
||||||
// Setup DOM
|
// Setup DOM
|
||||||
const doc = setupDOM('').window.document;
|
const doc = setupDOM('').window.document;
|
||||||
|
@ -88,7 +88,17 @@ export interface IconifyIconProps
|
|||||||
* Solid component
|
* Solid component
|
||||||
*/
|
*/
|
||||||
export function Icon(props: IconifyIconProps): JSX.Element {
|
export function Icon(props: IconifyIconProps): JSX.Element {
|
||||||
let { icon, mode, inline, rotate, flip, width, height } = props;
|
let {
|
||||||
|
icon,
|
||||||
|
mode,
|
||||||
|
inline,
|
||||||
|
rotate,
|
||||||
|
flip,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
viewBox,
|
||||||
|
preserveAspectRatio,
|
||||||
|
} = props;
|
||||||
|
|
||||||
// Convert icon to string
|
// Convert icon to string
|
||||||
if (typeof icon === 'object') {
|
if (typeof icon === 'object') {
|
||||||
@ -105,6 +115,8 @@ export function Icon(props: IconifyIconProps): JSX.Element {
|
|||||||
attr:flip={flip}
|
attr:flip={flip}
|
||||||
attr:width={width}
|
attr:width={width}
|
||||||
attr:height={height}
|
attr:height={height}
|
||||||
|
attr:viewBox={viewBox}
|
||||||
|
attr:preserveAspectRatio={preserveAspectRatio}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user