diff --git a/components/svelte/tsconfig-base.json b/components/svelte/tsconfig-base.json index 4a368d1..475695e 100644 --- a/components/svelte/tsconfig-base.json +++ b/components/svelte/tsconfig-base.json @@ -7,7 +7,7 @@ "declaration": true, "sourceMap": false, "strict": true, - "types": ["node", "svelte"], + "types": ["svelte"], "moduleResolution": "node", "allowSyntheticDefaultImports": true, "esModuleInterop": true, diff --git a/packages/core/src/builder/functions.ts b/packages/core/src/builder/functions.ts index 50621a8..ecb9c68 100644 --- a/packages/core/src/builder/functions.ts +++ b/packages/core/src/builder/functions.ts @@ -31,8 +31,6 @@ export function buildIcon( ): IconifyIconBuildResult { return iconToSVG( { ...defaultIconProps, ...icon }, - customisations - ? mergeCustomisations(defaultIconCustomisations, customisations) - : defaultIconCustomisations + mergeCustomisations(defaultIconCustomisations, customisations || {}) ); } diff --git a/packages/utils/src/customisations/merge.ts b/packages/utils/src/customisations/merge.ts index 1519ba4..97774a6 100644 --- a/packages/utils/src/customisations/merge.ts +++ b/packages/utils/src/customisations/merge.ts @@ -1,4 +1,3 @@ -import { mergeIconTransformations } from '../icon/transformations'; import { defaultIconSizeCustomisations, FullIconCustomisations, @@ -7,29 +6,35 @@ import { } from './defaults'; /** - * Convert IconifyIconCustomisations to FullIconCustomisations + * Convert IconifyIconCustomisations to FullIconCustomisations, checking value types */ export function mergeCustomisations( defaults: T, - item: IconifyIconCustomisations, - keepOtherProps = true + item: IconifyIconCustomisations ): T { - // Merge transformations - const result = mergeIconTransformations(defaults, item, keepOtherProps); + // Copy default values + const result = { + ...defaults, + }; - // Merge dimensions - for (const key in defaultIconSizeCustomisations) { - const attr = key as keyof IconifyIconSizeCustomisations; - - const value = item[attr]; + // Merge all properties + for (const key in item) { + const value = item[key as keyof IconifyIconCustomisations]; const valueType = typeof value; - if ( - value === null || - (value && (valueType === 'string' || valueType === 'number')) - ) { - result[attr] = value; - } else { - (result as Record)[attr] = defaults[attr]; + + if (key in defaultIconSizeCustomisations) { + // Dimension + if ( + value === null || + (value && (valueType === 'string' || valueType === 'number')) + ) { + result[key as keyof IconifyIconSizeCustomisations] = + value as string; + } + } else if (valueType === typeof result[key as keyof T]) { + // Normalise rotation, copy everything else as is + (result as Record)[key] = + key === 'rotate' ? (value as number) % 4 : value; } } diff --git a/packages/utils/src/icon-set/get-icon.ts b/packages/utils/src/icon-set/get-icon.ts index 663496a..83ff91d 100644 --- a/packages/utils/src/icon-set/get-icon.ts +++ b/packages/utils/src/icon-set/get-icon.ts @@ -1,6 +1,5 @@ -import type { IconifyJSON } from '@iconify/types'; -import { defaultIconProps } from '../icon/defaults'; -import type { IconifyIcon, FullIconifyIcon } from '../icon/defaults'; +import type { ExtendedIconifyIcon, IconifyJSON } from '@iconify/types'; +import { defaultIconProps, FullExtendedIconifyIcon } from '../icon/defaults'; import { mergeIconData } from '../icon/merge'; import { getIconsTree } from './tree'; @@ -12,30 +11,29 @@ export function internalGetIconData( name: string, tree: string[], full: true -): FullIconifyIcon; +): FullExtendedIconifyIcon; export function internalGetIconData( data: IconifyJSON, name: string, tree: string[], full: false -): IconifyIcon; +): ExtendedIconifyIcon; export function internalGetIconData( data: IconifyJSON, name: string, tree: string[], full: boolean -): FullIconifyIcon | IconifyIcon { +): FullExtendedIconifyIcon | ExtendedIconifyIcon { const icons = data.icons; const aliases = data.aliases || {}; - let currentProps = {} as IconifyIcon; + let currentProps = {} as ExtendedIconifyIcon; // Parse parent item function parse(name: string) { currentProps = mergeIconData( icons[name] || aliases[name], - currentProps, - false + currentProps ); } @@ -45,9 +43,8 @@ export function internalGetIconData( // Add default values currentProps = mergeIconData( data, - currentProps, - false - ) as unknown as IconifyIcon; + currentProps + ) as unknown as ExtendedIconifyIcon; // Return icon return full @@ -62,17 +59,17 @@ export function getIconData( data: IconifyJSON, name: string, full: true -): FullIconifyIcon | null; +): FullExtendedIconifyIcon | null; export function getIconData( data: IconifyJSON, name: string, full: false -): IconifyIcon | null; +): ExtendedIconifyIcon | null; export function getIconData( data: IconifyJSON, name: string, full = false -): FullIconifyIcon | IconifyIcon | null { +): FullExtendedIconifyIcon | ExtendedIconifyIcon | null { if (data.icons[name]) { // Parse only icon return internalGetIconData(data, name, [], full as true); diff --git a/packages/utils/src/icon-set/get-icons.ts b/packages/utils/src/icon-set/get-icons.ts index bed5504..70ae7db 100644 --- a/packages/utils/src/icon-set/get-icons.ts +++ b/packages/utils/src/icon-set/get-icons.ts @@ -1,4 +1,4 @@ -import type { IconifyAliases, IconifyJSON } from '@iconify/types'; +import type { IconifyAliases, IconifyIcons, IconifyJSON } from '@iconify/types'; import { defaultIconDimensions } from '../icon/defaults'; import { getIconsTree } from './tree'; @@ -17,7 +17,7 @@ export function getIcons( names: string[], not_found?: boolean ): IconifyJSON | null { - const icons = Object.create(null) as IconifyJSON['icons']; + const icons = Object.create(null) as IconifyIcons; const aliases = Object.create(null) as IconifyAliases; const result: IconifyJSON = { prefix: data.prefix, diff --git a/packages/utils/src/icon-set/parse.ts b/packages/utils/src/icon-set/parse.ts index 93f5981..9ffa954 100644 --- a/packages/utils/src/icon-set/parse.ts +++ b/packages/utils/src/icon-set/parse.ts @@ -1,5 +1,5 @@ import type { IconifyJSON } from '@iconify/types'; -import type { FullIconifyIcon } from '../icon/defaults'; +import type { FullExtendedIconifyIcon } from '../icon/defaults'; import { internalGetIconData } from './get-icon'; import { getIconsTree } from './tree'; @@ -10,7 +10,7 @@ import { getIconsTree } from './tree'; */ export type SplitIconSetCallback = ( name: string, - data: FullIconifyIcon | null + data: FullExtendedIconifyIcon | null ) => void; /** diff --git a/packages/utils/src/icon-set/tree.ts b/packages/utils/src/icon-set/tree.ts index c12b98f..6cb3624 100644 --- a/packages/utils/src/icon-set/tree.ts +++ b/packages/utils/src/icon-set/tree.ts @@ -43,7 +43,7 @@ export function getIconsTree( } // Resolve only required icons - (names || Object.keys(aliases).concat(Object.keys(icons))).forEach(resolve); + (names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve); return resolved; } diff --git a/packages/utils/src/icon-set/validate-basic.ts b/packages/utils/src/icon-set/validate-basic.ts index 7f487bc..4e75d54 100644 --- a/packages/utils/src/icon-set/validate-basic.ts +++ b/packages/utils/src/icon-set/validate-basic.ts @@ -1,6 +1,9 @@ import type { IconifyJSON } from '@iconify/types'; import { matchIconName } from '../icon/name'; -import { defaultIconDimensions, defaultIconProps } from '../icon/defaults'; +import { + defaultIconDimensions, + defaultExtendedIconProps, +} from '../icon/defaults'; type PropsList = Record; @@ -65,7 +68,10 @@ export function quicklyValidateIconSet(obj: unknown): IconifyJSON | null { if ( !name.match(matchIconName) || typeof icon.body !== 'string' || - !checkOptionalProps(icon as unknown as PropsList, defaultIconProps) + !checkOptionalProps( + icon as unknown as PropsList, + defaultExtendedIconProps + ) ) { return null; } @@ -80,7 +86,10 @@ export function quicklyValidateIconSet(obj: unknown): IconifyJSON | null { !name.match(matchIconName) || typeof parent !== 'string' || (!icons[parent] && !aliases[parent]) || - !checkOptionalProps(icon as unknown as PropsList, defaultIconProps) + !checkOptionalProps( + icon as unknown as PropsList, + defaultExtendedIconProps + ) ) { return null; } diff --git a/packages/utils/src/icon-set/validate.ts b/packages/utils/src/icon-set/validate.ts index 11c3f7b..ae1040d 100644 --- a/packages/utils/src/icon-set/validate.ts +++ b/packages/utils/src/icon-set/validate.ts @@ -4,7 +4,7 @@ import type { IconifyOptional, } from '@iconify/types'; import { matchIconName } from '../icon/name'; -import { defaultIconProps } from '../icon/defaults'; +import { defaultExtendedIconProps } from '../icon/defaults'; import { getIconsTree } from './tree'; /** @@ -34,10 +34,9 @@ function validateIconProps( continue; } - const expectedType = - key === 'hidden' - ? 'boolean' - : typeof (defaultIconProps as Record)[attr]; + const expectedType = typeof ( + defaultExtendedIconProps as Record + )[attr]; if (expectedType !== 'undefined') { if (type !== expectedType) { diff --git a/packages/utils/src/icon/defaults.ts b/packages/utils/src/icon/defaults.ts index 6ad034d..9067630 100644 --- a/packages/utils/src/icon/defaults.ts +++ b/packages/utils/src/icon/defaults.ts @@ -3,12 +3,20 @@ import type { IconifyTransformations, IconifyOptional, IconifyIcon, + ExtendedIconifyIcon, } from '@iconify/types'; // Export icon and full icon types export { IconifyIcon }; + export type FullIconifyIcon = Required; +// Partial and full extended icon +export type PartialExtendedIconifyIcon = Partial; + +type IconifyIconExtraProps = Omit; +export type FullExtendedIconifyIcon = FullIconifyIcon & IconifyIconExtraProps; + /** * Default values for dimensions */ @@ -38,3 +46,13 @@ export const defaultIconProps: Required = Object.freeze({ ...defaultIconDimensions, ...defaultIconTransformations, }); + +/** + * Default values for all properties used in ExtendedIconifyIcon + */ +export const defaultExtendedIconProps: Required = + Object.freeze({ + ...defaultIconProps, + body: '', + hidden: false, + }); diff --git a/packages/utils/src/icon/merge.ts b/packages/utils/src/icon/merge.ts index d541545..816072c 100644 --- a/packages/utils/src/icon/merge.ts +++ b/packages/utils/src/icon/merge.ts @@ -1,43 +1,42 @@ -import type { - IconifyDimenisons, - IconifyIcon, - IconifyOptional, - IconifyTransformations, -} from '@iconify/types'; -import { defaultIconDimensions } from './defaults'; +import type { IconifyTransformations } from '@iconify/types'; +import { + defaultExtendedIconProps, + defaultIconTransformations, + PartialExtendedIconifyIcon, +} from './defaults'; import { mergeIconTransformations } from './transformations'; -// Props to copy: all icon properties, except transformations -type PropsToCopy = Omit; -const propsToMerge: Required = { - ...defaultIconDimensions, - body: '', -}; - /** * Merge icon and alias * * Can also be used to merge default values and icon */ -export function mergeIconData( +export function mergeIconData( parent: T, - child: IconifyOptional | IconifyIcon, - keepOtherParentProps = true + child: PartialExtendedIconifyIcon ): T { - // Merge transformations - const result = mergeIconTransformations( - parent, - child, - keepOtherParentProps - ); + // Merge transformations and add defaults + const result = mergeIconTransformations(parent, child); // Merge icon properties that aren't transformations - for (const key in propsToMerge) { - const prop = key as keyof IconifyDimenisons; - if (child[prop] !== void 0) { - result[prop] = child[prop]; - } else if (parent[prop] !== void 0) { - result[prop] = parent[prop]; + for (const key in defaultExtendedIconProps) { + // Add default transformations if needed + if ( + defaultIconTransformations[key as keyof IconifyTransformations] !== + void 0 + ) { + if ( + result[key as 'rotate'] === void 0 && + parent[key as keyof T] !== void 0 + ) { + result[key as 'rotate'] = + defaultIconTransformations[key as 'rotate']; + } + // Not transformation + } else if (child[key as 'width'] !== void 0) { + result[key as 'width'] = child[key as 'width']; + } else if (parent[key as 'width'] !== void 0) { + result[key as 'width'] = parent[key as 'width']; } } diff --git a/packages/utils/src/icon/transformations.ts b/packages/utils/src/icon/transformations.ts index 047cfe6..9efcbdf 100644 --- a/packages/utils/src/icon/transformations.ts +++ b/packages/utils/src/icon/transformations.ts @@ -5,18 +5,18 @@ import type { IconifyTransformations } from '@iconify/types'; */ export function mergeIconTransformations( obj1: T, - obj2: IconifyTransformations, - keepOtherProps = true + obj2: IconifyTransformations ): T { - const result = keepOtherProps ? { ...obj1 } : ({} as T); - if (obj1.hFlip || obj2.hFlip) { - result.hFlip = obj1.hFlip !== obj2.hFlip; + const result = {} as T; + if (!obj1.hFlip !== !obj2.hFlip) { + result.hFlip = true; } - if (obj1.vFlip || obj2.vFlip) { - result.vFlip = obj1.vFlip !== obj2.vFlip; + if (!obj1.vFlip !== !obj2.vFlip) { + result.vFlip = true; } - if (obj1.rotate || obj2.rotate) { - result.rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4; + const rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4; + if (rotate) { + result.rotate = rotate; } return result; } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d4fdcdf..f85ea97 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -19,6 +19,7 @@ export { // Icon data export { mergeIconData } from './icon/merge'; +export { mergeIconTransformations } from './icon/transformations'; export { defaultIconProps, defaultIconDimensions, diff --git a/packages/utils/src/svg/build.ts b/packages/utils/src/svg/build.ts index 752bc55..ccd0df4 100644 --- a/packages/utils/src/svg/build.ts +++ b/packages/utils/src/svg/build.ts @@ -160,7 +160,8 @@ export function iconToSVG( const boxWidth = box.width; const boxHeight = box.height; - let width, height; + let width: string | number; + let height: string | number; if (customisationsWidth === null) { // Width is not set: calculate width from height, default to '1em' height = @@ -181,15 +182,11 @@ export function iconToSVG( : customisationsHeight; } - // Convert to string - width = typeof width === 'string' ? width : width.toString(); - height = typeof height === 'string' ? height : height.toString(); - // Result const result: IconifyIconBuildResult = { attributes: { - width, - height, + width: width.toString(), + height: height.toString(), viewBox: box.left.toString() + ' ' + diff --git a/packages/utils/tests/merge-icon-data-test.ts b/packages/utils/tests/merge-icon-data-test.ts index 432e68a..ff79f39 100644 --- a/packages/utils/tests/merge-icon-data-test.ts +++ b/packages/utils/tests/merge-icon-data-test.ts @@ -2,8 +2,7 @@ import type { IconifyIcon } from '@iconify/types'; import { mergeIconData } from '../lib/icon/merge'; describe('Testing merging icon data', () => { - test('Test', () => { - // Nothing to merge + test('Nothing to merge', () => { const icon: IconifyIcon = { body: '', }; @@ -13,9 +12,10 @@ describe('Testing merging icon data', () => { // Check hint manually: supposed to be IconifyIcon const result = mergeIconData(icon, {}); expect(result).toEqual(expected); + }); - // TypeScript full icon test - const icon2: Required = { + test('Full icons', () => { + const icon: Required = { body: '', width: 24, height: 24, @@ -25,7 +25,7 @@ describe('Testing merging icon data', () => { hFlip: false, vFlip: false, }; - const expected2: Required = { + const expected: Required = { body: '', width: 24, height: 24, @@ -36,9 +36,11 @@ describe('Testing merging icon data', () => { vFlip: false, }; // Check hint manually: supposed to be Required - const result2 = mergeIconData(icon2, {}); - expect(result2).toEqual(expected2); + const result = mergeIconData(icon, {}); + expect(result).toEqual(expected); + }); + test('Copy values', () => { // Copy values expect( mergeIconData( @@ -55,8 +57,9 @@ describe('Testing merging icon data', () => { width: 24, height: 32, }); + }); - // Override values + test('Override values', () => { expect( mergeIconData( { @@ -74,4 +77,30 @@ describe('Testing merging icon data', () => { height: 32, }); }); + + test('Override transformations', () => { + expect( + mergeIconData( + { + body: '', + width: 24, + height: 24, + hFlip: true, + rotate: 3, + }, + { + height: 32, + vFlip: true, + rotate: 2, + } + ) + ).toEqual({ + body: '', + width: 24, + height: 32, + hFlip: true, + vFlip: true, + rotate: 1, + }); + }); }); diff --git a/packages/utils/tests/svg-build-test.ts b/packages/utils/tests/svg-build-test.ts index 06a0342..027843b 100644 --- a/packages/utils/tests/svg-build-test.ts +++ b/packages/utils/tests/svg-build-test.ts @@ -4,7 +4,6 @@ import type { FullIconifyIcon } from '../lib/icon/defaults'; import { defaultIconProps } from '../lib/icon/defaults'; import type { FullIconCustomisations } from '../lib/customisations/defaults'; import { defaultIconCustomisations } from '../lib/customisations/defaults'; -import { mergeCustomisations } from '../lib/customisations/merge'; import { iconToHTML } from '../lib/svg/html'; describe('Testing iconToSVG', () => { @@ -31,12 +30,10 @@ describe('Testing iconToSVG', () => { }); test('Auto size, body', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: 'auto', - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: 'auto', + }; const icon: FullIconifyIcon = { ...defaultIconProps, body: '', @@ -66,12 +63,10 @@ describe('Testing iconToSVG', () => { }); test('Auto size, body', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: 'auto', - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: 'auto', + }; const icon: FullIconifyIcon = { ...defaultIconProps, body: '', @@ -90,12 +85,10 @@ describe('Testing iconToSVG', () => { }); test('Custom size', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: 'auto', - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: 'auto', + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -116,13 +109,11 @@ describe('Testing iconToSVG', () => { }); test('Rotation', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: '40px', - rotate: 1, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: '40px', + rotate: 1, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -143,13 +134,11 @@ describe('Testing iconToSVG', () => { }); test('Negative rotation', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: '40px', - rotate: -1, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: '40px', + rotate: -1, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -170,13 +159,11 @@ describe('Testing iconToSVG', () => { }); test('Flip', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - height: '32', - hFlip: true, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + height: '32', + hFlip: true, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -197,13 +184,11 @@ describe('Testing iconToSVG', () => { }); test('Flip, rotation', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - hFlip: true, - rotate: 1, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + hFlip: true, + rotate: 1, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -224,12 +209,10 @@ describe('Testing iconToSVG', () => { }); test('Flip icon that is rotated by default', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - hFlip: true, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + hFlip: true, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -254,16 +237,14 @@ describe('Testing iconToSVG', () => { }); test('Flip and rotation canceling eachother', () => { - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - { - width: '1em', - height: 'auto', - hFlip: true, - vFlip: true, - rotate: 2, - } - ); + const custom: FullIconCustomisations = { + ...defaultIconCustomisations, + width: '1em', + height: 'auto', + hFlip: true, + vFlip: true, + rotate: 2, + }; const icon: FullIconifyIcon = { ...defaultIconProps, width: 20, @@ -287,10 +268,7 @@ describe('Testing iconToSVG', () => { const iconBody = ''; - const custom: FullIconCustomisations = mergeCustomisations( - defaultIconCustomisations, - {} - ); + const custom: FullIconCustomisations = defaultIconCustomisations; const icon: FullIconifyIcon = { ...defaultIconProps, body: iconBody,