diff --git a/package-lock.json b/package-lock.json index 09e0824..f908b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4460,7 +4460,6 @@ "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", - "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" }, "bin": { @@ -5245,9 +5244,6 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6" - }, "optionalDependencies": { "graceful-fs": "^4.1.6" } diff --git a/packages/core/package.json b/packages/core/package.json index d383654..b30f1d5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -90,14 +90,6 @@ "require": "./lib/icon/sort.js", "import": "./lib/icon/sort.mjs" }, - "./lib/modern": { - "require": "./lib/modern/index.js", - "import": "./lib/modern/index.mjs" - }, - "./lib/modern/index": { - "require": "./lib/modern/index.js", - "import": "./lib/modern/index.mjs" - }, "./lib/storage/functions": { "require": "./lib/storage/functions.js", "import": "./lib/storage/functions.mjs" @@ -108,17 +100,13 @@ } }, "dependencies": { - "@antfu/utils": "^0.3.0", "@iconify/api-redundancy": "^1.0.2", "@iconify/types": "^1.0.10", "@iconify/utils": "^1.0.16", - "cross-fetch": "^3.1.4", - "debug": "^4.3.3", - "local-pkg": "^0.4.0" + "cross-fetch": "^3.1.4" }, "devDependencies": { "@iconify/library-builder": "^1.0.3", - "@types/debug": "^4.1.7", "@types/jest": "^27.0.2", "@types/node": "^15.3.0", "@typescript-eslint/eslint-plugin": "^4.31.1", diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json index bd91973..804fdc4 100644 --- a/packages/utils/package-lock.json +++ b/packages/utils/package-lock.json @@ -13,7 +13,8 @@ "@antfu/utils": "^0.3.0", "@iconify/types": "^1.0.12", "debug": "^4.3.3", - "kolorist": "^1.5.0" + "kolorist": "^1.5.0", + "local-pkg": "^0.4.0" }, "devDependencies": { "@iconify/library-builder": "^1.0.4", @@ -3972,6 +3973,17 @@ "node": ">= 0.8.0" } }, + "node_modules/local-pkg": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz", + "integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8412,6 +8424,11 @@ "type-check": "~0.4.0" } }, + "local-pkg": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz", + "integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==" + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/packages/utils/package.json b/packages/utils/package.json index c24a5d4..ae6e620 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -128,6 +128,10 @@ "require": "./lib/loader/loaders.js", "import": "./lib/loader/loaders.mjs" }, + "./lib/loader/modern": { + "require": "./lib/loader/modern.js", + "import": "./lib/loader/modern.mjs" + }, "./lib/loader/types": { "require": "./lib/loader/types.js", "import": "./lib/loader/types.mjs" @@ -158,7 +162,8 @@ "@antfu/utils": "^0.3.0", "@iconify/types": "^1.0.12", "debug": "^4.3.3", - "kolorist": "^1.5.0" + "kolorist": "^1.5.0", + "local-pkg": "^0.4.0" }, "devDependencies": { "@iconify/library-builder": "^1.0.4", diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index b7be4a8..495ae24 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -48,11 +48,14 @@ export { stringToColor, compareColors, colorToString } from './colors/index'; export type { CustomIconLoader, CustomCollections, + IconCustomizer, + IconCustomizations, InlineCollection, } from './loader/types'; -export { tryInstallPkg } from './loader/utils'; +export { tryInstallPkg, mergeIconProps } from './loader/utils'; export { FileSystemIconLoader } from './loader/loaders'; export { getCustomIcon } from './loader/custom'; +export { loadCollection, searchForIcon } from './loader/modern'; // Misc export { camelize, camelToKebab, pascalize } from './misc/strings'; diff --git a/packages/utils/src/loader/custom.ts b/packages/utils/src/loader/custom.ts index 26137c8..a02d512 100644 --- a/packages/utils/src/loader/custom.ts +++ b/packages/utils/src/loader/custom.ts @@ -1,6 +1,6 @@ -import type { Awaitable } from '@antfu/utils'; import createDebugger from 'debug'; -import type { CustomIconLoader, InlineCollection } from './types'; +import type { CustomIconLoader, IconCustomizations, InlineCollection } from './types'; +import { mergeIconProps } from './utils'; const debug = createDebugger('@iconify-loader:custom'); @@ -11,7 +11,7 @@ export async function getCustomIcon( custom: CustomIconLoader | InlineCollection, collection: string, icon: string, - transform?: (svg: string) => Awaitable + iconsCustomizations?: IconCustomizations, ): Promise { let result: string | undefined | null; @@ -26,10 +26,18 @@ export async function getCustomIcon( if (result) { if (!result.startsWith('> = {}; const isLegacyExists = isPackageExists('@iconify/json'); @@ -48,13 +51,14 @@ export async function loadCollection(name: string, autoInstall = false): Promise } } -export function searchForIcon( +export async function searchForIcon( iconSet: IconifyJSON, collection: string, ids: string[], - customize?: (defaultCustomizations: FullIconCustomisations) => FullIconCustomisations -): string | null { + iconCustomizations?: IconCustomizations, +): Promise { let iconData: FullIconifyIcon | null; + const { customize, additionalProps = {}, iconCustomizer } = iconCustomizations || {} for (const id of ids) { iconData = getIconData(iconSet, id, true); if (iconData) { @@ -64,8 +68,14 @@ export function searchForIcon( iconData, typeof customize === 'function' ? customize(defaultCustomizations) : defaultCustomizations ); - return ` `${i[0]}="${i[1]}"`).join(' ')}>${body}`; + return await mergeIconProps( + `${body}`, + collection, + id, + additionalProps, + () => attributes, + iconCustomizer, + ) } } - return null; } diff --git a/packages/utils/src/loader/types.ts b/packages/utils/src/loader/types.ts index 90fe59c..e7a68bf 100644 --- a/packages/utils/src/loader/types.ts +++ b/packages/utils/src/loader/types.ts @@ -1,10 +1,54 @@ import type { Awaitable } from '@antfu/utils'; +import type { FullIconCustomisations } from '../customisations'; /** - * Custom icon loader, used by getCustomIcon() + * Custom icon loader, used by `getCustomIcon`. */ export type CustomIconLoader = (name: string) => Awaitable; +/** + * Custom icon customizer, it will allow to customize all icons on a collection or individual icons. + */ +export type IconCustomizer = (collection: string, icon: string, props: Record) => Awaitable; + +/** + * Icon customizations: will be applied to all resolved icons. + * + * For each loaded icon, the customizations will be applied in this order: + * - apply `transform` to raw `svg`, if provided and using custom icon collection + * - apply `customize` with default customizations, if provided + * - apply `iconCustomizer` with `customize` customizations, if provided + * - apply `additionalProps` with `iconCustomizer` customizations, if provided + */ +export type IconCustomizations = { + /** + * Transform raw `svg`. + * + * **WARNING**: `transform` will be only applied when using `custom` icon collection: it will be applied only when using `getCustomIcon` and excluded when using `searchForIcon`. + * + * @param svg The loaded `svg` + * @return The transformed `svg`. + */ + transform?: (svg: string) => Awaitable + /** + * Change default icon customizations values. + * + * @param defaultCustomizations Default icon's customizations values. + * @return The modified icon's customizations values. + */ + customize?: (defaultCustomizations: FullIconCustomisations) => FullIconCustomisations + /** + * Custom icon customizer. + */ + iconCustomizer?: IconCustomizer + /** + * Additional icon properties. + * + * All properties without value will not be applied. + */ + additionalProps?: Record +}; + /** * List of icons as object. Key is icon name, value is icon data or callback (can be async) to get icon data */ diff --git a/packages/utils/src/loader/utils.ts b/packages/utils/src/loader/utils.ts index 282b1e9..8950116 100644 --- a/packages/utils/src/loader/utils.ts +++ b/packages/utils/src/loader/utils.ts @@ -1,6 +1,7 @@ import { installPackage } from '@antfu/install-pkg'; -import { sleep } from '@antfu/utils'; +import { Awaitable, sleep } from '@antfu/utils'; import { cyan, yellow } from 'kolorist'; +import type { IconCustomizer } from './types'; const warned = new Set(); @@ -14,6 +15,25 @@ export function warnOnce(msg: string): void { let pending: Promise | undefined; const tasks: Record | undefined> = {}; +export async function mergeIconProps( + svg: string, + collection: string, + icon: string, + additionalProps: Record, + propsProvider?: () => Awaitable>, + iconCustomizer?: IconCustomizer, +): Promise { + const props: Record = await propsProvider?.() ?? {} + await iconCustomizer?.(collection, icon, props) + Object.keys(additionalProps).forEach((p) => { + const v = additionalProps[p] + if (v !== undefined && v !== null) + props[p] = v + }) + const replacement = svg.startsWith(' `${p}="${props[p]}"`).join(' ')}`) +} + export async function tryInstallPkg(name: string): Promise { if (pending) { await pending; diff --git a/packages/utils/tests/get-custom-icon-test.ts b/packages/utils/tests/get-custom-icon-test.ts index e505820..26a5576 100644 --- a/packages/utils/tests/get-custom-icon-test.ts +++ b/packages/utils/tests/get-custom-icon-test.ts @@ -16,9 +16,12 @@ describe('Testing getCustomIcon', () => { () => svg, 'a', 'b', - (icon) => { - return icon.replace(' -1).toBeTruthy(); expect(result && result.indexOf('height="1em"') > -1).toBeTruthy();