diff --git a/.eslintignore b/.eslintignore
index ffae92223..afce5388f 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -8,3 +8,4 @@ node_modules
docs/images
docs/**/examples/
packages/lucide-react/dynamicIconImports.js
+packages/angular/.angular
diff --git a/packages/angular/scripts/exportTemplate.mts b/packages/angular/scripts/exportTemplate.mts
index 3ea7f9bde..a7a7bd3ea 100644
--- a/packages/angular/scripts/exportTemplate.mts
+++ b/packages/angular/scripts/exportTemplate.mts
@@ -16,10 +16,10 @@ export default defineExportTemplate(async ({
const angularComponentName = `Lucide${componentName}`;
const selectors = [`svg[lucide${toPascalCase(iconName)}]`];
const aliasComponentNames: string[] = [];
- for (const alias of aliases) {
- const aliasName = typeof alias === 'string' ? alias : alias.name;
- const aliasComponentName = `Lucide${toPascalCase(aliasName)}`;
- const aliasSelector = `svg[lucide${toPascalCase(aliasName)}]`;
+ const aliasNames = aliases.map(alias => typeof alias === 'string' ? alias : alias.name);
+ for (const alias of aliasNames) {
+ const aliasComponentName = `Lucide${toPascalCase(alias)}`;
+ const aliasSelector = `svg[lucide${toPascalCase(alias)}]`;
if (!selectors.includes(aliasSelector)) {
selectors.push(aliasSelector);
}
@@ -49,10 +49,12 @@ import { Component, signal } from '@angular/core';
standalone: true,
})
export class ${angularComponentName} extends LucideIconBase {
- static readonly iconName = '${iconName}';
- static readonly iconData: LucideIconData = ${JSON.stringify(children)};
- protected override readonly iconName = signal(${angularComponentName}.iconName);
- protected override readonly iconData = signal(${angularComponentName}.iconData);
+ static readonly icon: LucideIconData = ${JSON.stringify({
+ name: iconName,
+ node: children,
+ ...(aliasNames.length > 0 && { aliases: aliasNames }),
+ })};
+ protected override readonly icon = signal(${angularComponentName}.icon);
}
${aliasComponentNames.map((aliasComponentName) => {
diff --git a/packages/angular/src/lucide-icon.spec.ts b/packages/angular/src/lucide-dynamic-icon.spec.ts
similarity index 63%
rename from packages/angular/src/lucide-icon.spec.ts
rename to packages/angular/src/lucide-dynamic-icon.spec.ts
index e6f2652f9..3a1ee7229 100644
--- a/packages/angular/src/lucide-icon.spec.ts
+++ b/packages/angular/src/lucide-dynamic-icon.spec.ts
@@ -1,5 +1,6 @@
import { Component, input, inputBinding, signal, WritableSignal } from '@angular/core';
-import { LucideIcon } from './lucide-icon';
+import { LucideDynamicIcon } from './lucide-dynamic-icon';
+import { provideLucideConfig } from './lucide-config';
import { LucideIconData, LucideIconInput } from './types';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideLucideIcons } from './lucide-icons';
@@ -12,44 +13,39 @@ import { By } from '@angular/platform-browser';
}`,
- imports: [LucideIcon],
+ imports: [LucideDynamicIcon],
})
class TestHostComponent {
readonly icon = input();
}
-describe('LucideIcon', () => {
- let component: LucideIcon;
- let fixture: ComponentFixture;
+describe('LucideDynamicIcon', () => {
+ let component: LucideDynamicIcon;
+ let fixture: ComponentFixture;
let icon: WritableSignal;
- let name: WritableSignal;
let title: WritableSignal;
let color: WritableSignal;
let size: WritableSignal;
let strokeWidth: WritableSignal;
let absoluteStrokeWidth: WritableSignal;
const getSvgAttribute = (attr: string) => fixture.nativeElement.getAttribute(attr);
- const testIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]];
- const testIcon2: LucideIconData = [
- ['circle', { cx: 12, cy: 12, r: 8 }],
- ['polyline', { points: '1 1 22 22' }],
- ];
- beforeEach(async () => {
- TestBed.configureTestingModule({
- providers: [provideLucideIcons({ demo: testIcon })],
- });
- icon = signal('demo');
- name = signal(undefined);
- title = signal(undefined);
- color = signal(undefined);
- size = signal(undefined);
- strokeWidth = signal(undefined);
- absoluteStrokeWidth = signal(undefined);
- fixture = TestBed.createComponent(LucideIcon, {
+ const testIcon: LucideIconData = {
+ name: 'demo',
+ node: [['polyline', { points: '1 1 22 22' }]],
+ };
+ const testIcon2: LucideIconData = {
+ name: 'demo-other',
+ node: [
+ ['circle', { cx: 12, cy: 12, r: 8 }],
+ ['polyline', { points: '1 1 22 22' }],
+ ],
+ aliases: ['demo-2'],
+ };
+ function createComponent() {
+ return TestBed.createComponent(LucideDynamicIcon, {
inferTagName: true,
bindings: [
inputBinding('lucideIcon', icon),
- inputBinding('name', name),
inputBinding('title', title),
inputBinding('color', color),
inputBinding('size', size),
@@ -57,6 +53,18 @@ describe('LucideIcon', () => {
inputBinding('absoluteStrokeWidth', absoluteStrokeWidth),
],
});
+ }
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ providers: [provideLucideIcons(testIcon)],
+ });
+ icon = signal('demo');
+ title = signal(undefined);
+ color = signal(undefined);
+ size = signal(undefined);
+ strokeWidth = signal(undefined);
+ absoluteStrokeWidth = signal(undefined);
+ fixture = createComponent();
component = fixture.componentInstance;
});
@@ -82,30 +90,23 @@ describe('LucideIcon', () => {
describe('iconInput', () => {
it('should support LucideIconData input', () => {
icon.set(testIcon);
- name.set('custom-name');
fixture.detectChanges();
-
- expect(component['iconData']()).toBe(testIcon);
- expect(component['iconName']()).toBe('custom-name');
+ expect(component['icon']()).toBe(testIcon);
expect(fixture.nativeElement.innerHTML).toBe(
'',
);
});
- it('should support LucideIconComponentType input', () => {
+ it('should support LucideIcon input', () => {
icon.set(LucideActivity);
fixture.detectChanges();
-
- expect(component['iconData']()).toBe(LucideActivity.iconData);
- expect(component['iconName']()).toBe(LucideActivity.iconName);
+ expect(component['icon']()).toBe(LucideActivity.icon);
});
it('should support string icon name', () => {
icon.set('demo');
fixture.detectChanges();
-
- expect(component['iconData']()).toBe(testIcon);
- expect(component['iconName']()).toBe('demo');
+ expect(component['icon']()).toBe(testIcon);
});
- it('should throw error if no icon founds', () => {
+ it('should throw error if no icon found', () => {
icon.set('invalid');
expect(() => fixture.detectChanges()).toThrowError(`Unable to resolve icon 'invalid'`);
});
@@ -116,12 +117,10 @@ describe('LucideIcon', () => {
fixture.detectChanges();
expect(getSvgAttribute('class')).toBe('lucide lucide-demo');
});
- it('should add class from name, even if icon has name', () => {
- icon.set(LucideActivity);
- name.set('custom-name');
+ it('should add backwards compatible classes from aliases', () => {
+ icon.set(testIcon2);
fixture.detectChanges();
-
- expect(getSvgAttribute('class')).toBe('lucide lucide-custom-name');
+ expect(getSvgAttribute('class')).toBe('lucide lucide-demo-other lucide-demo-2');
});
it('should add class icon if available', () => {
icon.set(LucideActivity);
@@ -195,16 +194,21 @@ describe('LucideIcon', () => {
it('should not adjust stroke width', () => {
strokeWidth.set(2);
size.set(12);
- absoluteStrokeWidth.set(false);
+ absoluteStrokeWidth.set(true);
fixture.detectChanges();
expect(getSvgAttribute('stroke-width')).toBe('2');
});
- it('should adjust stroke width', () => {
- strokeWidth.set(2);
- size.set(12);
+ it('should not set vector-effect on children', () => {
+ absoluteStrokeWidth.set(false);
+ for (const child of fixture.nativeElement.children) {
+ expect(child.getAttribute('vector-effect')).toBeNull();
+ }
+ });
+ it('should set vector-effect on children', () => {
absoluteStrokeWidth.set(true);
- fixture.detectChanges();
- expect(getSvgAttribute('stroke-width')).toBe('4');
+ for (const child of fixture.nativeElement.children) {
+ expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke');
+ }
});
});
@@ -240,4 +244,72 @@ describe('LucideIcon', () => {
expect(rect.outerHTML).toBe('');
});
});
+
+ describe('LUCIDE_CONFIG', () => {
+ beforeEach(async () => {
+ TestBed.resetTestingModule();
+ TestBed.configureTestingModule({
+ providers: [
+ provideLucideIcons(testIcon),
+ provideLucideConfig({
+ color: 'red',
+ strokeWidth: 1,
+ size: 12,
+ absoluteStrokeWidth: true,
+ }),
+ ],
+ });
+ await TestBed.compileComponents();
+ fixture = createComponent();
+ component = fixture.componentInstance;
+ });
+ describe('color', () => {
+ it('should use color from config', () => {
+ fixture.detectChanges();
+ expect(getSvgAttribute('stroke')).toBe('red');
+ });
+ it('should use override color from config', () => {
+ color.set('pink');
+ fixture.detectChanges();
+ expect(getSvgAttribute('stroke')).toBe('pink');
+ });
+ });
+ describe('strokeWidth', () => {
+ it('should use stroke width from config', () => {
+ fixture.detectChanges();
+ expect(getSvgAttribute('stroke-width')).toBe('1');
+ });
+ it('should use override stroke width from config', () => {
+ strokeWidth.set(3);
+ fixture.detectChanges();
+ expect(getSvgAttribute('stroke-width')).toBe('3');
+ });
+ });
+ describe('size', () => {
+ it('should use size from config', () => {
+ fixture.detectChanges();
+ expect(getSvgAttribute('width')).toBe('12');
+ expect(getSvgAttribute('height')).toBe('12');
+ });
+ it('should use override size from config', () => {
+ size.set('48');
+ fixture.detectChanges();
+ expect(getSvgAttribute('width')).toBe('48');
+ expect(getSvgAttribute('height')).toBe('48');
+ });
+ });
+ describe('absoluteStrokeWidth', () => {
+ it('should use absoluteStrokeWidth from config', () => {
+ for (const child of fixture.nativeElement.children) {
+ expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke');
+ }
+ });
+ it('should override absoluteStrokeWidth', () => {
+ absoluteStrokeWidth.set(false);
+ for (const child of fixture.nativeElement.children) {
+ expect(child.getAttribute('vector-effect')).toBeNull();
+ }
+ });
+ });
+ });
});
diff --git a/packages/angular/src/lucide-dynamic-icon.ts b/packages/angular/src/lucide-dynamic-icon.ts
new file mode 100644
index 000000000..ce678672d
--- /dev/null
+++ b/packages/angular/src/lucide-dynamic-icon.ts
@@ -0,0 +1,33 @@
+import { Component, computed, inject, input } from '@angular/core';
+import { isLucideIconComponent, isLucideIconData, LucideIconInput } from './types';
+import { LucideIconBase } from './lucide-icon-base';
+import { LUCIDE_ICONS } from './lucide-icons';
+
+/**
+ * Generic icon component for rendering LucideIconData.
+ */
+@Component({
+ selector: 'svg[lucideIcon]',
+ templateUrl: './lucide-icon.html',
+ standalone: true,
+})
+export class LucideDynamicIcon extends LucideIconBase {
+ protected readonly icons = inject(LUCIDE_ICONS);
+ public readonly lucideIcon = input.required();
+
+ protected override readonly icon = computed(() => {
+ const icon = this.lucideIcon();
+ if (isLucideIconData(icon)) {
+ return icon;
+ } else if (isLucideIconComponent(icon)) {
+ return icon.icon;
+ } else if (typeof icon === 'string') {
+ if (icon in this.icons) {
+ return this.icons[icon];
+ } else {
+ throw new Error(`Unable to resolve icon '${icon}'`);
+ }
+ }
+ return null;
+ });
+}
diff --git a/packages/angular/src/lucide-icon-base.ts b/packages/angular/src/lucide-icon-base.ts
index 39c688a53..d02a2f3f0 100644
--- a/packages/angular/src/lucide-icon-base.ts
+++ b/packages/angular/src/lucide-icon-base.ts
@@ -11,8 +11,6 @@ import {
import { LUCIDE_CONFIG } from './lucide-config';
import { LucideIconData, Nullable } from './types';
import defaultAttributes from './default-attributes';
-import { formatFixed } from './utils/format-fixed';
-import { toKebabCase } from './utils/to-kebab-case';
function transformNumericStringInput(
value: Nullable,
@@ -41,13 +39,12 @@ function transformNumericStringInput(
'[attr.width]': 'size().toString(10)',
'[attr.height]': 'size().toString(10)',
'[attr.stroke]': 'color()',
- '[attr.stroke-width]': 'computedStrokeWidth()',
+ '[attr.stroke-width]': 'strokeWidth().toString(10)',
'[attr.aria-hidden]': '!title()',
},
})
export abstract class LucideIconBase {
- protected abstract readonly iconName: Signal>;
- protected abstract readonly iconData: Signal>;
+ protected abstract readonly icon: Signal>;
protected readonly iconConfig = inject(LUCIDE_CONFIG);
protected readonly elRef = inject(ElementRef);
protected readonly renderer = inject(Renderer2);
@@ -88,33 +85,27 @@ export abstract class LucideIconBase {
transformNumericStringInput(value, this.iconConfig.strokeWidth),
});
/**
- * Whether stroke width should be scaled to appear uniform regardless of icon size.
- *
- * @remarks
- * Use CSS to set on SVG paths instead:
- * ```css
- * .lucide * {
- * vector-effect: non-scaling-stroke;
- * }
- * ```
+ * If set to true, it adds [`vector-effect="non-scaling-stroke"`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/vector-effect) to child elements.
*/
readonly absoluteStrokeWidth = input(this.iconConfig.absoluteStrokeWidth, {
transform: (value: Nullable) => value ?? this.iconConfig.absoluteStrokeWidth,
});
- protected readonly computedStrokeWidth = computed(() => {
- const strokeWidth = this.strokeWidth();
- const size = this.size();
- return this.absoluteStrokeWidth()
- ? formatFixed(strokeWidth / (size / 24))
- : strokeWidth.toString(10);
- });
constructor() {
effect((onCleanup) => {
- const icon = this.iconData();
+ const icon = this.icon();
if (icon) {
- const elements = icon.map(([name, attrs]) => {
+ const absoluteStrokeWidth = this.absoluteStrokeWidth();
+ const { name, node, aliases = [] } = icon;
+ const classes = [name, ...aliases].map((item) => `lucide-${item}`);
+ for (const cssClass of classes) {
+ this.renderer.addClass(this.elRef.nativeElement, cssClass);
+ }
+ const elements = node.map(([name, attrs]) => {
const element = this.renderer.createElement(name, 'http://www.w3.org/2000/svg');
+ if (absoluteStrokeWidth) {
+ this.renderer.setAttribute(element, 'vector-effect', 'non-scaling-stroke');
+ }
Object.entries(attrs).forEach(([name, value]) =>
this.renderer.setAttribute(
element,
@@ -129,16 +120,9 @@ export abstract class LucideIconBase {
elements.forEach((element) =>
this.renderer.removeChild(this.elRef.nativeElement, element),
);
- });
- }
- });
- effect((onCleanup) => {
- const name = this.iconName();
- if (name) {
- const cssClass = `lucide-${toKebabCase(name)}`;
- this.renderer.addClass(this.elRef.nativeElement, cssClass);
- onCleanup(() => {
- this.renderer.removeClass(this.elRef.nativeElement, cssClass);
+ for (const cssClass of classes) {
+ this.renderer.removeClass(this.elRef.nativeElement, cssClass);
+ }
});
}
});
diff --git a/packages/angular/src/lucide-icon.ts b/packages/angular/src/lucide-icon.ts
deleted file mode 100644
index eb718f418..000000000
--- a/packages/angular/src/lucide-icon.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Component, computed, inject, input } from '@angular/core';
-import { isLucideIconComponent, isLucideIconData, LucideIconInput } from './types';
-import { LucideIconBase } from './lucide-icon-base';
-import { LUCIDE_ICONS } from './lucide-icons';
-import { LucideIconData } from './types';
-import { toKebabCase } from './utils/to-kebab-case';
-
-interface LucideResolvedIcon {
- name?: string | null;
- data: LucideIconData;
-}
-
-/**
- * Generic icon component for rendering LucideIconData.
- */
-@Component({
- selector: 'svg[lucideIcon]',
- templateUrl: './lucide-icon.html',
- standalone: true,
-})
-export class LucideIcon extends LucideIconBase {
- protected readonly icons = inject(LUCIDE_ICONS);
- readonly name = input();
- readonly iconInput = input.required({
- alias: 'lucideIcon',
- });
- readonly resolvedIcon = computed(() => {
- return this.resolveIcon(this.name(), this.iconInput());
- });
- protected override readonly iconName = computed(() => {
- return this.resolvedIcon()?.name;
- });
- protected override readonly iconData = computed(() => {
- return this.resolvedIcon()?.data;
- });
-
- protected resolveIcon(
- name: string | null | undefined,
- icon: LucideIconInput | null | undefined,
- ): LucideResolvedIcon | null {
- if (isLucideIconData(icon)) {
- return {
- name,
- data: icon,
- };
- } else if (isLucideIconComponent(icon)) {
- return {
- name: name ?? icon.iconName,
- data: icon.iconData,
- };
- } else if (typeof icon === 'string') {
- const name = toKebabCase(icon);
- if (name in this.icons) {
- return {
- name,
- data: this.icons[name],
- };
- } else {
- throw new Error(`Unable to resolve icon '${icon}'`);
- }
- }
-
- return null;
- }
-}
diff --git a/packages/angular/src/lucide-icons.spec.ts b/packages/angular/src/lucide-icons.spec.ts
index 063867ed9..dba1f358d 100644
--- a/packages/angular/src/lucide-icons.spec.ts
+++ b/packages/angular/src/lucide-icons.spec.ts
@@ -1,5 +1,10 @@
import { TestBed } from '@angular/core/testing';
-import { LUCIDE_ICONS, provideLucideIcons } from './lucide-icons';
+import {
+ LUCIDE_ICONS,
+ lucideLegacyIcon,
+ lucideLegacyIconMap,
+ provideLucideIcons,
+} from './lucide-icons';
import { LucideIconData } from './types';
import { LucideActivity } from './icons/activity';
import { LucideCircle } from './icons/circle';
@@ -12,32 +17,49 @@ describe('Lucide icons', () => {
});
});
describe('provideLucideIcons', () => {
- const mockIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]];
- const mockIcon2: LucideIconData = [['circle', { cx: 12, cy: 12, r: 8 }]];
- it('should accept dictionary of icons', () => {
+ const mockIcon: LucideIconData = {
+ name: 'mock-icon',
+ node: [['polyline', { points: '1 1 22 22' }]],
+ };
+ const mockIcon2: LucideIconData = {
+ name: 'mock-icon-circle',
+ node: [['circle', { cx: 12, cy: 12, r: 8 }]],
+ aliases: ['mock-icon-2'],
+ };
+ const legacyIconNode: LucideIconData['node'] = [['circle', { cx: 12, cy: 12, r: 8 }]];
+ const legacyAlias = 'legacy-old-name';
+ const OtherLegacyIcon = legacyIconNode;
+ it('should accept list of icon object, icon components or legacy icons', () => {
TestBed.configureTestingModule({
providers: [
- provideLucideIcons({
- DemoIcon: mockIcon,
- MockIcon: mockIcon2,
- TestIcon: LucideActivity,
- }),
+ provideLucideIcons(
+ mockIcon,
+ mockIcon2,
+ LucideCircle,
+ lucideLegacyIcon('legacy-icon', legacyIconNode, [legacyAlias]),
+ ...lucideLegacyIconMap({ OtherLegacyIcon }),
+ ),
],
});
+ const legacyIconData = {
+ name: 'legacy-icon',
+ node: legacyIconNode,
+ aliases: [legacyAlias],
+ };
+ const otherLegacyIconData = {
+ name: 'other-legacy-icon',
+ node: legacyIconNode,
+ aliases: ['OtherLegacyIcon'],
+ };
expect(TestBed.inject(LUCIDE_ICONS)).toEqual({
- 'demo-icon': mockIcon,
- 'mock-icon': mockIcon2,
- [LucideActivity.iconName]: LucideActivity.iconData,
- });
- });
- it('should accept list of icon components', () => {
- TestBed.configureTestingModule({
- providers: [provideLucideIcons([LucideActivity, LucideSquareX, LucideCircle])],
- });
- expect(TestBed.inject(LUCIDE_ICONS)).toEqual({
- [LucideActivity.iconName]: LucideActivity.iconData,
- [LucideSquareX.iconName]: LucideSquareX.iconData,
- [LucideCircle.iconName]: LucideCircle.iconData,
+ 'mock-icon': mockIcon,
+ 'mock-icon-circle': mockIcon2,
+ 'mock-icon-2': mockIcon2,
+ 'legacy-icon': legacyIconData,
+ 'legacy-old-name': legacyIconData,
+ 'other-legacy-icon': otherLegacyIconData,
+ OtherLegacyIcon: otherLegacyIconData,
+ ['circle']: LucideCircle.icon,
});
});
});
diff --git a/packages/angular/src/lucide-icons.ts b/packages/angular/src/lucide-icons.ts
index 9fa0f8fe6..0f3136b35 100644
--- a/packages/angular/src/lucide-icons.ts
+++ b/packages/angular/src/lucide-icons.ts
@@ -1,7 +1,5 @@
import { InjectionToken, Provider } from '@angular/core';
-import { LucideIconData, LucideIcons } from './types';
-import { isLucideIconComponent, LucideIconComponentType } from './types';
-import { toKebabCase } from './utils/to-kebab-case';
+import { isLucideIconComponent, LucideIcon, LucideIconData, LucideIcons } from './types';
/**
* Injection token for providing Lucide icons by name.
@@ -23,7 +21,7 @@ export const LUCIDE_ICONS = new InjectionToken('Lucide icons', {
* @usage
* ```ts
* import { provideLucideIcons, SquareCheck } from '@lucide/angular';
- * import { MyCustomIcon } from './custom-icons/circle-check';
+ * import { MyCustomIcon } from './custom-icons/my-custom-icon';
*
* providers: [
* provideLucideIcons({
@@ -37,28 +35,71 @@ export const LUCIDE_ICONS = new InjectionToken('Lucide icons', {
*
* ```
*/
-export function provideLucideIcons(
- icons: Record | Array,
-): Provider {
- if (Array.isArray(icons)) {
- return {
- provide: LUCIDE_ICONS,
- useValue: icons.reduce((acc, icon) => {
- acc[toKebabCase(icon.iconName)] = icon.iconData;
- return acc;
- }, {} as LucideIcons),
- };
- } else {
- return {
- provide: LUCIDE_ICONS,
- useValue: Object.entries(icons).reduce((acc, [name, icon]) => {
- if (isLucideIconComponent(icon)) {
- acc[icon.iconName] = icon.iconData;
- } else {
- acc[toKebabCase(name)] = icon;
- }
- return acc;
- }, {} as LucideIcons),
- };
- }
+export function provideLucideIcons(...icons: Array): Provider {
+ return {
+ provide: LUCIDE_ICONS,
+ useValue: icons.reduce((acc, icon) => {
+ const iconData = isLucideIconComponent(icon) ? icon.icon : icon;
+ acc[iconData.name] = iconData;
+ for (const alias of iconData.aliases ?? []) {
+ acc[alias] = iconData;
+ }
+ return acc;
+ }, {} as LucideIcons),
+ };
+}
+
+/**
+ * Converts a legacy icon node to the new format, for custom icon (e.g. `@lucide/lab`) support.
+ *
+ * @usage
+ * ```ts
+ * import { provideLucideIcons, lucideLegacyIcon } from '@lucide/angular';
+ * import { UserRoundX } from 'lucide-angular';
+ * import { burger } from '@lucide/lab';
+ *
+ * provideLucideIcons(
+ * lucideLegacyIcon('user-round-x', UserRoundX, ['user-circle-x']),
+ * lucideLegacyIcon('burger', burger, ['hamburger']),
+ * ),
+ * ```
+ */
+export function lucideLegacyIcon(
+ name: string,
+ node: LucideIconData['node'],
+ aliases: string[] = [],
+): LucideIconData {
+ return {
+ name,
+ node,
+ aliases,
+ };
+}
+
+/**
+ * Converts a map of legacy icon nodes to a list of icon data objects.
+ *
+ * @usage
+ * ```ts
+ * import { provideLucideIcons, lucideLegacyIconMap, LucideCircle } from '@lucide/angular';
+ * import { UserRoundX } from 'lucide-angular';
+ * import { burger } from '@lucide/lab';
+ *
+ * provideLucideIcons(
+ * LucideCircle,
+ * ...lucideLegacyIconMap({ UserRoundX, burger }),
+ * ),
+ * ```
+ */
+export function lucideLegacyIconMap(
+ icons: Record,
+): LucideIconData[] {
+ return Object.entries(icons).map(([pascalName, node]) => {
+ const name: string = pascalName.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
+ return {
+ name,
+ node,
+ aliases: [pascalName],
+ };
+ });
}
diff --git a/packages/angular/src/public-api.ts b/packages/angular/src/public-api.ts
index 51d13d982..3bee2b36d 100644
--- a/packages/angular/src/public-api.ts
+++ b/packages/angular/src/public-api.ts
@@ -1,7 +1,7 @@
import * as icons from './icons/lucide-angular';
export * from './lucide-config';
-export * from './lucide-icon';
+export * from './lucide-dynamic-icon';
export * from './lucide-icons';
export * from './types';
export * from './icons/lucide-angular';
diff --git a/packages/angular/src/types.ts b/packages/angular/src/types.ts
index a3f7b23b4..9e210dca1 100644
--- a/packages/angular/src/types.ts
+++ b/packages/angular/src/types.ts
@@ -2,44 +2,57 @@ import { Signal, Type } from '@angular/core';
type HtmlAttributes = { [key: string]: string | number };
export type LucideIconNode = readonly [string, HtmlAttributes];
-export type LucideIconData = readonly LucideIconNode[];
export type LucideIcons = { [key: string]: LucideIconData };
/**
- * Represents a Lucide icon component that has `iconName` and `iconData` signals inherited from `LucideIconBase` and respective static members accessible without instantiating the component.
+ * A Lucide icon object that fully describes an icon to be displayed.
*/
-export type LucideIconComponentType = Type<{
+export type LucideIconData = {
+ name: string;
+ node: LucideIconNode[];
+ aliases?: string[];
+};
+
+/**
+ * Input signal map of Lucide icon components.
+ */
+interface LucideIconProps {
title: Signal>;
size: Signal>;
color: Signal>;
strokeWidth: Signal>;
absoluteStrokeWidth: Signal>;
-}> & {
- iconName: string;
- iconData: LucideIconData;
-};
+}
+
+/**
+ * Represents a Lucide icon component type that has `iconName` and `iconData` signals inherited from `LucideIconBase` and respective static members accessible without instantiating the component.
+ */
+export interface LucideIcon extends Type {
+ icon: LucideIconData;
+}
/**
* Type guard for {@link LucideIconData}
*/
export function isLucideIconData(icon: unknown): icon is LucideIconData {
- return Array.isArray(icon);
-}
-
-/**
- * Type guard for {@link LucideIconComponentType}
- */
-export function isLucideIconComponent(icon: unknown): icon is LucideIconComponentType {
return (
- icon instanceof Type &&
- 'iconData' in icon &&
- Array.isArray(icon.iconData) &&
- 'iconName' in icon &&
- typeof icon.iconName === 'string'
+ !!icon &&
+ typeof icon === 'object' &&
+ 'name' in icon &&
+ typeof icon.name === 'string' &&
+ 'node' in icon &&
+ Array.isArray(icon.node)
);
}
-export type LucideIconInput = LucideIconComponentType | LucideIconData | string;
+/**
+ * Type guard for {@link LucideIcon}
+ */
+export function isLucideIconComponent(icon: unknown): icon is LucideIcon {
+ return icon instanceof Type && 'icon' in icon && isLucideIconData(icon.icon);
+}
+
+export type LucideIconInput = LucideIcon | LucideIconData | string;
/**
* @internal
diff --git a/packages/angular/src/utils/format-fixed.ts b/packages/angular/src/utils/format-fixed.ts
deleted file mode 100644
index 0a35fc24b..000000000
--- a/packages/angular/src/utils/format-fixed.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function formatFixed(number: number, decimals = 3): string {
- return parseFloat(number.toFixed(decimals)).toString(10);
-}
diff --git a/packages/angular/src/utils/to-kebab-case.ts b/packages/angular/src/utils/to-kebab-case.ts
deleted file mode 100644
index 8752ee821..000000000
--- a/packages/angular/src/utils/to-kebab-case.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export const toKebabCase = (name: string) =>
- name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();