From eb158561d3429e47cb827ecf3f40ea811289b0b0 Mon Sep 17 00:00:00 2001 From: Eric Fennis Date: Fri, 7 Feb 2025 15:44:54 +0100 Subject: [PATCH] fix(lucide): Support for iconNodes from other libs like Lab (#2752) * Add support for iconNodes * formatting --- packages/lucide/package.json | 1 + packages/lucide/scripts/exportTemplate.mjs | 6 +--- packages/lucide/src/createElement.ts | 17 +++++------ packages/lucide/src/replaceElement.ts | 14 ++++++---- packages/lucide/src/types.ts | 3 +- ...ucide.spec.js.snap => lucide.spec.ts.snap} | 0 ...ec.js.snap => replaceElement.spec.ts.snap} | 0 .../tests/{lucide.spec.js => lucide.spec.ts} | 28 +++++++++++-------- ...Element.spec.js => replaceElement.spec.ts} | 4 +-- 9 files changed, 38 insertions(+), 35 deletions(-) rename packages/lucide/tests/__snapshots__/{lucide.spec.js.snap => lucide.spec.ts.snap} (100%) rename packages/lucide/tests/__snapshots__/{replaceElement.spec.js.snap => replaceElement.spec.ts.snap} (100%) rename packages/lucide/tests/{lucide.spec.js => lucide.spec.ts} (79%) rename packages/lucide/tests/{replaceElement.spec.js => replaceElement.spec.ts} (92%) diff --git a/packages/lucide/package.json b/packages/lucide/package.json index 595cad6cf8e..957ef3dedb9 100644 --- a/packages/lucide/package.json +++ b/packages/lucide/package.json @@ -39,6 +39,7 @@ "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --iconFileExtension=.ts --withAliases --aliasNamesOnly --aliasesFileExtension=.ts --exportFileName=index.ts", "build:bundle": "rollup -c rollup.config.mjs", "test": "pnpm build:icons && vitest run", + "test:watch": "vitest watch", "version": "pnpm version --git-tag-version=false" }, "devDependencies": { diff --git a/packages/lucide/scripts/exportTemplate.mjs b/packages/lucide/scripts/exportTemplate.mjs index 910dca95764..a756e3bab64 100644 --- a/packages/lucide/scripts/exportTemplate.mjs +++ b/packages/lucide/scripts/exportTemplate.mjs @@ -19,11 +19,7 @@ import type { IconNode } from '../types'; * @returns {Array} * ${deprecated ? `@deprecated ${deprecationReason}` : ''} */ -const ${componentName}: IconNode = [ - 'svg', - defaultAttributes, - ${JSON.stringify(children)} -]; +const ${componentName}: IconNode = ${JSON.stringify(children)} export default ${componentName}; `; diff --git a/packages/lucide/src/createElement.ts b/packages/lucide/src/createElement.ts index fcb4a4da3a4..874caa50bcf 100644 --- a/packages/lucide/src/createElement.ts +++ b/packages/lucide/src/createElement.ts @@ -1,4 +1,6 @@ -import { IconNode, IconNodeChild, SVGProps } from './types'; +import { IconNode, SVGProps } from './types'; + +type CreateElementParams = [tag: string, attrs: SVGProps, children?: IconNode]; /** * Creates a new HTMLElement from icon node @@ -7,16 +9,16 @@ import { IconNode, IconNodeChild, SVGProps } from './types'; * @param {array} children * @returns {HTMLElement} */ -const createElement = (tag: string, attrs: SVGProps, children: IconNodeChild[] = []) => { +const createElement = ([tag, attrs, children]: CreateElementParams) => { const element = document.createElementNS('http://www.w3.org/2000/svg', tag); Object.keys(attrs).forEach((name) => { element.setAttribute(name, String(attrs[name])); }); - if (children.length) { + if (children?.length) { children.forEach((child) => { - const childElement = createElement(...child); + const childElement = createElement(child); element.appendChild(childElement); }); @@ -25,9 +27,4 @@ const createElement = (tag: string, attrs: SVGProps, children: IconNodeChild[] = return element; }; -/** - * Creates a new HTMLElement from icon node - * @param {[tag: string, attrs: object, children: array]} iconNode - * @returns {HTMLElement} - */ -export default ([tag, attrs, children]: IconNode) => createElement(tag, attrs, children); +export default createElement; diff --git a/packages/lucide/src/replaceElement.ts b/packages/lucide/src/replaceElement.ts index 75aabb2e20e..2b26a6b274d 100644 --- a/packages/lucide/src/replaceElement.ts +++ b/packages/lucide/src/replaceElement.ts @@ -1,4 +1,5 @@ import createElement from './createElement'; +import defaultAttributes from './defaultAttributes'; import { Icons } from './types'; export type CustomAttrs = { [attr: string]: any }; @@ -19,7 +20,9 @@ export const getAttrs = (element: Element): Record => * @param {Object} attrs * @returns {Array} */ -export const getClassNames = (attrs: Record | string): string | string[] => { +export const getClassNames = ( + attrs: Record | string, +): string | string[] => { if (typeof attrs === 'string') return attrs; if (!attrs || !attrs.class) return ''; if (attrs.class && typeof attrs.class === 'string') { @@ -36,7 +39,9 @@ export const getClassNames = (attrs: Record | string): string | * @param {array} arrayOfClassnames * @returns {string} */ -export const combineClassNames = (arrayOfClassnames: (string | Record)[]) => { +export const combineClassNames = ( + arrayOfClassnames: (string | Record)[], +) => { const classNameArray = arrayOfClassnames.flatMap(getClassNames); return classNameArray @@ -77,10 +82,9 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle } const elementAttrs = getAttrs(element); - const [tag, iconAttributes, children] = iconNode; const iconAttrs = { - ...iconAttributes, + ...defaultAttributes, 'data-lucide': iconName, ...attrs, ...elementAttrs, @@ -94,7 +98,7 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle }); } - const svgElement = createElement([tag, iconAttrs, children]); + const svgElement = createElement(['svg', iconAttrs, iconNode]); return element.parentNode?.replaceChild(svgElement, element); }; diff --git a/packages/lucide/src/types.ts b/packages/lucide/src/types.ts index 3b040d604d2..e0c21d63fb9 100644 --- a/packages/lucide/src/types.ts +++ b/packages/lucide/src/types.ts @@ -1,6 +1,5 @@ // className is not supported in svg elements export type SVGProps = Record; -export type IconNodeChild = readonly [tag: string, attrs: SVGProps]; -export type IconNode = readonly [tag: string, attrs: SVGProps, children?: IconNodeChild[]]; +export type IconNode = [tag: string, attrs: SVGProps][]; export type Icons = { [key: string]: IconNode }; diff --git a/packages/lucide/tests/__snapshots__/lucide.spec.js.snap b/packages/lucide/tests/__snapshots__/lucide.spec.ts.snap similarity index 100% rename from packages/lucide/tests/__snapshots__/lucide.spec.js.snap rename to packages/lucide/tests/__snapshots__/lucide.spec.ts.snap diff --git a/packages/lucide/tests/__snapshots__/replaceElement.spec.js.snap b/packages/lucide/tests/__snapshots__/replaceElement.spec.ts.snap similarity index 100% rename from packages/lucide/tests/__snapshots__/replaceElement.spec.js.snap rename to packages/lucide/tests/__snapshots__/replaceElement.spec.ts.snap diff --git a/packages/lucide/tests/lucide.spec.js b/packages/lucide/tests/lucide.spec.ts similarity index 79% rename from packages/lucide/tests/lucide.spec.js rename to packages/lucide/tests/lucide.spec.ts index 4d4a263940f..2cd621980ad 100644 --- a/packages/lucide/tests/lucide.spec.js +++ b/packages/lucide/tests/lucide.spec.ts @@ -6,7 +6,7 @@ import { parseSync, stringify } from 'svgson'; const ICONS_DIR = path.resolve(__dirname, '../../../icons'); -const getOriginalSvg = (iconName, aliasName) => { +const getOriginalSvg = (iconName: string, aliasName?: string) => { const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8'); const svgParsed = parseSync(svgContent); @@ -51,14 +51,17 @@ describe('createIcons', () => { createIcons({ icons, attrs }); - const element = document.querySelector('svg'); + const element = document.querySelector('svg') as SVGSVGElement; const attributes = element.getAttributeNames(); - const attributesAndValues = attributes.reduce((acc, item) => { - acc[item] = element.getAttribute(item); + const attributesAndValues = attributes.reduce( + (acc, item) => { + acc[item] = element.getAttribute(item); - return acc; - }, {}); + return acc; + }, + {} as Record, + ); expect(document.body.innerHTML).toMatchSnapshot(); @@ -74,14 +77,17 @@ describe('createIcons', () => { createIcons({ icons }); - const element = document.querySelector('svg'); + const element = document.querySelector('svg') as SVGSVGElement; const attributes = element.getAttributeNames(); - const attributesAndValues = attributes.reduce((acc, item) => { - acc[item] = element.getAttribute(item); + const attributesAndValues = attributes.reduce( + (acc, item) => { + acc[item] = element.getAttribute(item); - return acc; - }, {}); + return acc; + }, + {} as Record, + ); expect(attributesAndValues).toEqual(expect.objectContaining(attrs)); }); diff --git a/packages/lucide/tests/replaceElement.spec.js b/packages/lucide/tests/replaceElement.spec.ts similarity index 92% rename from packages/lucide/tests/replaceElement.spec.js rename to packages/lucide/tests/replaceElement.spec.ts index 8828c484362..16d2742aba2 100644 --- a/packages/lucide/tests/replaceElement.spec.js +++ b/packages/lucide/tests/replaceElement.spec.ts @@ -16,7 +16,7 @@ describe('getAtts', () => { ], }; - const attrs = getAttrs(element); + const attrs = getAttrs(element as unknown as Element); expect(attrs.class).toBe(element.attributes[0].value); }); @@ -44,7 +44,7 @@ describe('getClassNames', () => { describe('combineClassNames', () => { it('should returns a string of classNames', () => { - const arrayOfClassnames = [ + const arrayOfClassnames: (string | Record)[] = [ 'item', { class: ['item1', 'item2', 'item3'],