diff --git a/packages/utils/src/customisations/merge.ts b/packages/utils/src/customisations/merge.ts index 2861e36..1519ba4 100644 --- a/packages/utils/src/customisations/merge.ts +++ b/packages/utils/src/customisations/merge.ts @@ -11,10 +11,11 @@ import { */ export function mergeCustomisations( defaults: T, - item: IconifyIconCustomisations + item: IconifyIconCustomisations, + keepOtherProps = true ): T { // Merge transformations - const result = mergeIconTransformations(defaults, item); + const result = mergeIconTransformations(defaults, item, keepOtherProps); // Merge dimensions for (const key in defaultIconSizeCustomisations) { diff --git a/packages/utils/src/icon-set/get-icon.ts b/packages/utils/src/icon-set/get-icon.ts index d90fea5..8b43f25 100644 --- a/packages/utils/src/icon-set/get-icon.ts +++ b/packages/utils/src/icon-set/get-icon.ts @@ -1,7 +1,8 @@ -import type { IconifyDimenisons, IconifyJSON } from '@iconify/types'; -import { defaultIconProps, defaultIconDimensions } from '../icon/defaults'; +import type { IconifyJSON } from '@iconify/types'; +import { defaultIconProps } from '../icon/defaults'; import type { IconifyIcon, FullIconifyIcon } from '../icon/defaults'; import { mergeIconData } from '../icon/merge'; +import { getIconsTree } from './tree'; /** * Get data for icon @@ -21,54 +22,43 @@ export function getIconData( name: string, full = false ): FullIconifyIcon | IconifyIcon | null { - function getIcon(name: string, iteration: number): IconifyIcon | null { - if (data.icons[name] !== void 0) { - // Return icon - return Object.assign({}, data.icons[name]); - } + const icons = data.icons; + const aliases = data.aliases || {}; - // Check loop - if (iteration > 5) { + let currentProps = {} as IconifyIcon; + + // Parse parent item + function parse(name: string) { + currentProps = mergeIconData( + icons[name] || aliases[name], + currentProps, + false + ); + } + + const icon = icons[name]; + if (icon) { + // Parse only icon + parse(name); + } else { + // Resolve tree + const tree = getIconsTree(data, [name])[name]; + if (!tree) { return null; } - - // Check if alias exists - const aliases = data.aliases; - if (aliases && aliases[name] !== void 0) { - const item = aliases[name]; - const result = getIcon(item.parent, iteration + 1); - if (result) { - return mergeIconData(result, item); - } - return result; - } - - // Check if character exists - const chars = data.chars; - if (!iteration && chars && chars[name] !== void 0) { - return getIcon(chars[name], iteration + 1); - } - - return null; + parse(name); + tree.forEach(parse); } - const result = getIcon(name, 0); - - // Add default properties - if (result) { - for (const key in defaultIconDimensions) { - if ( - result[key as keyof IconifyDimenisons] === void 0 && - data[key as keyof IconifyDimenisons] !== void 0 - ) { - (result as unknown as Record)[key] = - data[key as keyof IconifyDimenisons]; - } - } - } + // Add default values + currentProps = mergeIconData( + data, + currentProps, + false + ) as unknown as IconifyIcon; // Return icon - return result && full - ? Object.assign({}, defaultIconProps, result) - : result; + return full + ? Object.assign({}, defaultIconProps, currentProps) + : currentProps; } diff --git a/packages/utils/src/icon-set/get-icons.ts b/packages/utils/src/icon-set/get-icons.ts index caa520a..bed5504 100644 --- a/packages/utils/src/icon-set/get-icons.ts +++ b/packages/utils/src/icon-set/get-icons.ts @@ -1,11 +1,11 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { IconifyJSON } from '@iconify/types'; -import { defaultIconProps } from '../icon/defaults'; +import type { IconifyAliases, IconifyJSON } from '@iconify/types'; +import { defaultIconDimensions } from '../icon/defaults'; +import { getIconsTree } from './tree'; /** * Optional properties that must be copied when copying icon set */ -export const propsToCopy = Object.keys(defaultIconProps).concat([ +export const propsToCopy = Object.keys(defaultIconDimensions).concat([ 'provider', ]) as (keyof IconifyJSON)[]; @@ -14,61 +14,43 @@ export const propsToCopy = Object.keys(defaultIconProps).concat([ */ export function getIcons( data: IconifyJSON, - icons: string[], + names: string[], not_found?: boolean ): IconifyJSON | null { + const icons = Object.create(null) as IconifyJSON['icons']; + const aliases = Object.create(null) as IconifyAliases; const result: IconifyJSON = { prefix: data.prefix, - icons: Object.create(null) as never, + icons, }; - const tested: Set = new Set(); + + const sourceIcons = data.icons; + const sourceAliases = data.aliases || {}; + + const tree = getIconsTree(data, names); let empty = true; - function copy(name: string, iteration: number): boolean { - if (iteration > 5 || tested.has(name)) { - // Already copied or too much nesting - return true; - } - tested.add(name); - - // Check for icon - if (data.icons[name] !== void 0) { + // Copy all icons + for (const name in tree) { + if (!tree[name]) { + // Failed + if (not_found && names.indexOf(name) !== -1) { + // Add to not_found + (result.not_found || (result.not_found = [])).push(name); + } + } else if (sourceIcons[name]) { + // Icon + icons[name] = { + ...sourceIcons[name], + }; empty = false; - result.icons[name] = { ...data.icons[name] }; - return true; + } else { + // Alias + aliases[name] = { + ...sourceAliases[name], + }; + result.aliases = aliases; } - - // Check for alias - const aliases = data.aliases; - if (aliases && aliases[name] !== void 0) { - const copied = copy(aliases[name].parent, iteration + 1); - if (copied) { - if (result.aliases === void 0) { - result.aliases = Object.create(null) as never; - } - result.aliases[name] = { ...aliases[name] }; - } - return copied; - } - - // Check for character, return as alias - const chars = data.chars; - if (chars && chars[name] !== void 0) { - const parent = chars[name]; - const copied = copy(parent, iteration + 1); - if (copied) { - if (result.aliases === void 0) { - result.aliases = Object.create(null) as never; - } - result.aliases[name] = { - parent, - }; - } - return copied; - } - - // Not found - return false; } // Copy common properties @@ -78,15 +60,5 @@ export function getIcons( } }); - // Copy all icons - icons.forEach((name) => { - if (!copy(name, 0) && not_found === true) { - if (result.not_found === void 0) { - result.not_found = []; - } - result.not_found.push(name); - } - }); - return empty && not_found !== true ? null : result; } diff --git a/packages/utils/src/icon-set/tree.ts b/packages/utils/src/icon-set/tree.ts index 55cb523..c12b98f 100644 --- a/packages/utils/src/icon-set/tree.ts +++ b/packages/utils/src/icon-set/tree.ts @@ -11,18 +11,20 @@ export type ParentIconsTree = Record; * * Returns parent icon for each icon */ -export function getIconsTree(data: IconifyJSON): ParentIconsTree { - const resolved = Object.create(null) as ParentIconsTree; - - // Add all icons - for (const key in data.icons) { - resolved[key] = []; - } - - // Add all aliases +export function getIconsTree( + data: IconifyJSON, + names?: string[] +): ParentIconsTree { + const icons = data.icons; const aliases = data.aliases || {}; - function resolveAlias(name: string): ParentIconsList | null { + const resolved = Object.create(null) as ParentIconsTree; + + function resolve(name: string): ParentIconsList | null { + if (icons[name]) { + return (resolved[name] = []); + } + if (resolved[name] === void 0) { // Mark as failed if parent alias points to this icon to avoid infinite loop resolved[name] = null; @@ -31,7 +33,7 @@ export function getIconsTree(data: IconifyJSON): ParentIconsTree { const parent = aliases[name] && aliases[name].parent; // Get value for parent - const value = parent && resolveAlias(parent); + const value = parent && resolve(parent); if (value) { resolved[name] = [parent].concat(value); } @@ -40,9 +42,8 @@ export function getIconsTree(data: IconifyJSON): ParentIconsTree { return resolved[name]; } - for (const name in aliases) { - resolveAlias(name); - } + // Resolve only required icons + (names || Object.keys(aliases).concat(Object.keys(icons))).forEach(resolve); return resolved; } diff --git a/packages/utils/src/icon/merge.ts b/packages/utils/src/icon/merge.ts index e1817da..d541545 100644 --- a/packages/utils/src/icon/merge.ts +++ b/packages/utils/src/icon/merge.ts @@ -1,22 +1,43 @@ -import type { IconifyDimenisons, IconifyOptional } from '@iconify/types'; +import type { + IconifyDimenisons, + IconifyIcon, + IconifyOptional, + IconifyTransformations, +} from '@iconify/types'; import { defaultIconDimensions } 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( - icon: T, - alias: IconifyOptional + parent: T, + child: IconifyOptional | IconifyIcon, + keepOtherParentProps = true ): T { - // Merge transformations, while keeping other props - const result = mergeIconTransformations(icon, alias); + // Merge transformations + const result = mergeIconTransformations( + parent, + child, + keepOtherParentProps + ); - // Merge dimensions - for (const key in defaultIconDimensions) { + // Merge icon properties that aren't transformations + for (const key in propsToMerge) { const prop = key as keyof IconifyDimenisons; - if (alias[prop] !== void 0) { - result[prop] = alias[prop]; + if (child[prop] !== void 0) { + result[prop] = child[prop]; + } else if (parent[prop] !== void 0) { + result[prop] = parent[prop]; } } diff --git a/packages/utils/src/icon/transformations.ts b/packages/utils/src/icon/transformations.ts index c392013..047cfe6 100644 --- a/packages/utils/src/icon/transformations.ts +++ b/packages/utils/src/icon/transformations.ts @@ -1,23 +1,22 @@ import type { IconifyTransformations } from '@iconify/types'; /** - * Merge transformations. Also copies other properties from first parameter + * Merge transformations */ export function mergeIconTransformations( obj1: T, - obj2: IconifyTransformations + obj2: IconifyTransformations, + keepOtherProps = true ): T { - const result = { - ...obj1, - }; - if (obj2.hFlip) { - result.hFlip = !result.hFlip; + const result = keepOtherProps ? { ...obj1 } : ({} as T); + if (obj1.hFlip || obj2.hFlip) { + result.hFlip = obj1.hFlip !== obj2.hFlip; } - if (obj2.vFlip) { - result.vFlip = !result.vFlip; + if (obj1.vFlip || obj2.vFlip) { + result.vFlip = obj1.vFlip !== obj2.vFlip; } - if (obj2.rotate) { - result.rotate = ((result.rotate || 0) + obj2.rotate) % 4; + if (obj1.rotate || obj2.rotate) { + result.rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4; } return result; } diff --git a/packages/utils/src/misc/objects.ts b/packages/utils/src/misc/objects.ts index c5ac4a7..88e8f67 100644 --- a/packages/utils/src/misc/objects.ts +++ b/packages/utils/src/misc/objects.ts @@ -33,3 +33,20 @@ export function unmergeObjects>( } return result; } + +/** + * Get common properties in 2 objects + */ +export function commonObjectProps>( + item: unknown, + reference: T +): Partial { + const result = {} as T; + for (const key in reference) { + const value = (item as T)[key]; + if (value !== void 0) { + result[key] = value; + } + } + return result; +} diff --git a/packages/utils/tests/get-icons-test.ts b/packages/utils/tests/get-icons-test.ts index c41bd19..7b605b1 100644 --- a/packages/utils/tests/get-icons-test.ts +++ b/packages/utils/tests/get-icons-test.ts @@ -137,38 +137,10 @@ describe('Testing retrieving icons from icon set', () => { }); // Character - expect(getIcons(data, ['f00'])).toEqual({ - prefix: 'foo', - icons: { - bar2: { - body: '', - }, - }, - aliases: { - f00: { - parent: 'bar2', - }, - }, - }); + expect(getIcons(data, ['f00'])).toBeNull(); // Character that points to alias - expect(getIcons(data, ['f02'])).toEqual({ - prefix: 'foo', - icons: { - bar: { - body: '', - }, - }, - aliases: { - f02: { - parent: 'foo', - }, - foo: { - parent: 'bar', - hFlip: true, - }, - }, - }); + expect(getIcons(data, ['f02'])).toBeNull(); // Bad character expect(getIcons(data, ['f04'])).toBeNull(); diff --git a/packages/utils/tests/parse-icons-test.ts b/packages/utils/tests/parse-icons-test.ts index f3e13a5..5450935 100644 --- a/packages/utils/tests/parse-icons-test.ts +++ b/packages/utils/tests/parse-icons-test.ts @@ -173,6 +173,7 @@ describe('Testing parsing icon set', () => { 'alias2z4', 'alias2z5', 'alias2z6', + 'alias2z7', ]; const namesCopy = names.slice(0); @@ -275,6 +276,17 @@ describe('Testing parsing icon set', () => { vFlip: true, rotate: 1, }, + alias2z7: { + // alias of alias2z6 + body: '', + width: 21, + height: 24, + top: 0, + left: 0, + hFlip: false, + vFlip: true, + rotate: 1, + }, }; // Do stuff @@ -333,7 +345,6 @@ describe('Testing parsing icon set', () => { }, alias2z7: { // 7 parents: alias2z6, alias2z5, alias2z4, alias2z3, alias2z, alias2f, icon2 - // nesting is too deep and should not be parsed parent: 'alias2z6', }, alias3: {