Compare commits

...

51 Commits

Author SHA1 Message Date
Eric Fennis
91506784a2 Format code 2025-12-12 10:55:29 +01:00
Eric Fennis
5528e13644 Apply feedback 2025-12-12 10:52:32 +01:00
Eric Fennis
636ae1d9e0 Add classname to context provider 2025-12-12 10:29:24 +01:00
Eric Fennis
83d95dd0e6 formatting 2025-12-12 09:44:23 +01:00
Eric Fennis
0031dc42e7 Merge branch 'next' of https://github.com/lucide-icons/lucide into context-providers 2025-12-12 09:35:11 +01:00
Eric Fennis
05a2315a17 Merge branch 'context-providers' of https://github.com/lucide-icons/lucide into context-providers 2025-11-23 09:54:47 +01:00
Eric Fennis
ec34048560 Remove fill form context providers 2025-11-23 09:54:44 +01:00
Eric Fennis
1f081aa276 Update packages/svelte/src/context.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-23 09:50:46 +01:00
Eric Fennis
f8c4b8bb48 Merge branch 'next' of https://github.com/lucide-icons/lucide into context-providers 2025-11-21 15:11:36 +01:00
Eric Fennis
6ec17b17a3 Formatting 2025-10-16 18:47:14 +02:00
Eric Fennis
2eb29ea0e8 Remove class 2025-10-16 18:44:33 +02:00
Eric Fennis
9fc85ccd32 apply feedback 2025-10-16 18:43:30 +02:00
Eric Fennis
1863791990 fix tests 2025-10-16 18:41:50 +02:00
Eric Fennis
afd31b0fa2 Fixes types and tests 2025-10-16 18:32:12 +02:00
Eric Fennis
2f9c69a3e1 fix export template 2025-10-16 18:24:27 +02:00
Eric Fennis
bf0824d216 apply feedback 2025-10-16 18:19:32 +02:00
Eric Fennis
4f74929c33 update export template 2025-10-16 18:17:36 +02:00
Eric Fennis
c9769ccddc Remove x 2025-10-16 18:11:52 +02:00
Eric Fennis
e5fcd3dc34 Update packages/vue/tests/context.spec.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-16 18:07:47 +02:00
Eric Fennis
6c3ab468c8 Update packages/lucide-preact/tests/context.spec.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-16 18:07:41 +02:00
Eric Fennis
26684bf954 Update packages/lucide-react/tests/context.spec.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-16 18:07:06 +02:00
Eric Fennis
1859b37c35 Move on 2025-10-16 18:05:24 +02:00
Eric Fennis
b08292951a Formatting 2025-10-16 17:57:23 +02:00
Eric Fennis
811144de12 Merge branch 'next' of https://github.com/lucide-icons/lucide into context-providers 2025-10-16 17:56:44 +02:00
Eric Fennis
92881531dd Cleanup 2025-10-16 17:41:43 +02:00
Eric Fennis
048a031bf9 Merge branch 'context-providers' of https://github.com/lucide-icons/lucide into context-providers 2025-10-16 17:40:49 +02:00
Eric Fennis
6c3a072781 Apply fixes 2025-10-16 17:40:46 +02:00
Eric Fennis
1c6a4031bb Update packages/lucide-react-native/tests/context.spec.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-16 17:40:34 +02:00
Eric Fennis
66b72c1db1 Improve memoization 2025-10-16 17:01:24 +02:00
Eric Fennis
2517a1e944 Merge branch 'main' of https://github.com/lucide-icons/lucide into context-providers 2025-08-08 10:24:35 +02:00
Eric Fennis
8771dbdae6 Add context provider for Vue 2025-07-04 18:51:21 +02:00
Eric Fennis
1b338ef4c6 Merge branch 'move-vue-package' of https://github.com/lucide-icons/lucide into context-providers 2025-07-04 17:46:04 +02:00
Eric Fennis
bad581f640 Merge branch 'main' into move-vue-package 2025-07-04 17:42:02 +02:00
Eric Fennis
66a95c1368 Format code 2025-07-04 17:41:44 +02:00
Eric Fennis
1e0149a827 Fix tests 2025-07-04 17:05:14 +02:00
Eric Fennis
1726441298 Adjust vue package! 2025-07-04 16:56:27 +02:00
Eric Fennis
471bd9b817 Merge branch 'main' of https://github.com/lucide-icons/lucide into move-vue-package 2025-07-04 16:24:59 +02:00
Eric Fennis
805785c0e7 Add context provider to preact 2025-07-04 16:00:55 +02:00
Eric Fennis
4281588a4c Implement context providers 2025-07-04 14:45:33 +02:00
Eric Fennis
99a3844ce2 Fix context 2025-07-04 11:17:34 +02:00
Eric Fennis
18d0220de9 Merge branch 'main' of https://github.com/lucide-icons/lucide into context-providers 2025-07-04 10:35:01 +02:00
Eric Fennis
54c75ac20f Merge branch 'main' of https://github.com/lucide-icons/lucide into context-providers 2025-06-18 20:22:08 +02:00
Eric Fennis
fb99da99a3 Add context provider to solid package 2025-06-18 20:21:58 +02:00
Eric Fennis
3cec076d1a Adjust export template 2025-06-18 17:07:38 +02:00
Eric Fennis
3dff23edf4 Merge branch 'main' of https://github.com/lucide-icons/lucide into move-vue-package 2025-06-18 15:56:48 +02:00
Eric Fennis
546af67faa Update docs 2025-06-18 15:46:51 +02:00
Eric Fennis
3dd09434c8 Remove old vue 2 doc 2025-06-18 15:39:55 +02:00
Eric Fennis
3fdbda43c1 Add @lucide/vue package 2025-06-18 15:39:13 +02:00
Eric Fennis
18c155cda0 Remove old vue 2 package 2025-06-18 15:25:58 +02:00
Eric Fennis
dc418c7011 Add context provider for react 2025-06-13 13:44:24 +02:00
Eric Fennis
1d48e8b33a Add context provider for svelte package 2025-06-13 13:16:19 +02:00
47 changed files with 1273 additions and 278 deletions

View File

@@ -37,6 +37,7 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts",
"build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {
@@ -47,7 +48,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/preact": "^3.2.3",
"jest-serializer-html": "^7.1.0",
"preact": "^10.19.2",
"preact": "^10.26.9",
"rollup": "^4.53.3",
"rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3",

View File

@@ -1,6 +1,8 @@
import { h, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import type { IconNode, LucideProps } from './types';
import { useLucideContext } from './context';
import { mergeClasses } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
@@ -22,29 +24,41 @@ interface IconComponentProps extends LucideProps {
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = ({
color = 'currentColor',
size = 24,
strokeWidth = 2,
color,
size,
strokeWidth,
absoluteStrokeWidth,
children,
iconNode,
class: classes = '',
...rest
}: IconComponentProps) =>
h(
}: IconComponentProps) => {
const {
size: contextSize = 24,
strokeWidth: contextStrokeWidth = 2,
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
color: contextColor = 'currentColor',
class: contextClass = '',
} = useLucideContext() ?? {};
const calculatedStrokeWidth =
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
: strokeWidth ?? contextStrokeWidth;
return h(
'svg',
{
...defaultAttributes,
width: String(size),
height: size,
stroke: color,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', classes].join(' '),
width: size ?? contextSize ?? 24,
height: size ?? contextSize ?? 24,
stroke: color ?? contextColor,
['stroke-width' as 'strokeWidth']: calculatedStrokeWidth,
class: mergeClasses('lucide', contextClass, classes),
...rest,
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
);
};
export default Icon;

View File

@@ -0,0 +1,49 @@
import { createContext, type ComponentChildren } from 'preact';
import { useContext, useMemo } from 'preact/hooks';
const LucideContext = createContext<{
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}>({
size: 24,
color: 'currentColor',
strokeWidth: 2,
absoluteStrokeWidth: false,
class: '',
});
interface LucideProviderProps {
children: ComponentChildren;
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export function LucideProvider({
children,
size,
color,
strokeWidth,
absoluteStrokeWidth,
class: className,
}: LucideProviderProps) {
const value = useMemo(
() => ({
size,
color,
strokeWidth,
absoluteStrokeWidth,
class: className,
}),
[size, color, strokeWidth, absoluteStrokeWidth, className],
);
return <LucideContext.Provider value={value}>{children}</LucideContext.Provider>;
}
export const useLucideContext = () => useContext(LucideContext);

View File

@@ -2,6 +2,7 @@ export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export * from './context';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -2,7 +2,7 @@
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide "
class="lucide"
fill="none"
height="48"
stroke="red"

View File

@@ -0,0 +1,23 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
class="lucide lucide-house"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
/>
</svg>
`;

View File

@@ -0,0 +1,88 @@
import { render } from '@testing-library/preact';
import { describe, expect, it } from 'vitest';
import { House, LucideProvider } from '../src/lucide-preact';
describe('Using LucideProvider', () => {
it('should render the icon with LucideProvider', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
>
<House />
</LucideProvider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
absoluteStrokeWidth
>
<House />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it("should override the provider's global props when passing props to the icon", () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House
size={24}
color="blue"
strokeWidth={2}
/>
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should merge class names from LucideProvider and icon props', () => {
const { container } = render(
<LucideProvider class="provider-class">
<House class="icon-class" />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('class', 'lucide provider-class lucide-house icon-class');
});
});

View File

@@ -38,6 +38,7 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts --withAliases --aliasesFileExtension=.ts",
"build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {

View File

@@ -2,9 +2,11 @@ import { createElement, forwardRef, type FunctionComponent } from 'react';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { useLucideContext } from './context';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
testID?: string;
}
/**
@@ -23,21 +25,22 @@ interface IconComponentProps extends LucideProps {
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
...rest
},
ref,
) => {
({ color, size, strokeWidth, absoluteStrokeWidth, children, iconNode, testID, ...rest }, ref) => {
const {
size: contextSize = 24,
strokeWidth: contextStrokeWidth = 2,
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
color: contextColor = 'currentColor',
} = useLucideContext() ?? {};
const calculatedStrokeWidth =
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
: strokeWidth ?? contextStrokeWidth;
const customAttrs = {
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
stroke: color ?? contextColor ?? defaultAttributes.stroke,
strokeWidth: calculatedStrokeWidth,
...rest,
};
@@ -46,8 +49,9 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
{
ref,
...defaultAttributes,
width: size,
height: size,
width: size ?? contextSize ?? defaultAttributes.width,
height: size ?? contextSize ?? defaultAttributes.height,
'data-testid': testID,
...customAttrs,
},
[

View File

@@ -0,0 +1,43 @@
import { createContext, type ReactNode, useContext, useMemo } from 'react';
const LucideContext = createContext<{
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
}>({
size: 24,
color: 'currentColor',
strokeWidth: 2,
absoluteStrokeWidth: false,
});
interface LucideProviderProps {
children: ReactNode;
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
}
export function LucideProvider({
children,
size,
color,
strokeWidth,
absoluteStrokeWidth,
}: LucideProviderProps) {
const value = useMemo(
() => ({
size,
color,
strokeWidth,
absoluteStrokeWidth,
}),
[size, color, strokeWidth, absoluteStrokeWidth],
);
return <LucideContext.Provider value={value}>{children}</LucideContext.Provider>;
}
export const useLucideContext = () => useContext(LucideContext);

View File

@@ -1,55 +1,24 @@
import { forwardRef, createElement, FunctionComponent } from 'react';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import { IconNode, LucideIcon, LucideProps } from './types';
import { forwardRef, createElement } from 'react';
import { IconNode, LucideProps } from './types';
import { toPascalCase } from '@lucide/shared';
import Icon from './Icon';
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
'data-testid': dataTestId,
...rest
}: LucideProps,
/**
* Create a Lucide icon component
* @param {string} iconName
* @param {array} iconNode
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const createLucideIcon = (iconName: string, iconNode: IconNode) => {
const Component = forwardRef<SVGSVGElement, LucideProps>((props, ref) =>
createElement(Icon, {
ref,
) => {
const customAttrs = {
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
...rest,
};
return createElement(
NativeSvg.Svg as unknown as string,
{
ref,
...defaultAttributes,
width: size,
height: size,
'data-testid': dataTestId,
...customAttrs,
},
[
...iconNode.map(([tag, attrs]) => {
const upperCasedTag = (tag.charAt(0).toUpperCase() +
tag.slice(1)) as keyof typeof NativeSvg;
// duplicating the attributes here because generating the OTA update bundles don't inherit the SVG properties from parent (codepush, expo-updates)
return createElement(
NativeSvg[upperCasedTag] as FunctionComponent<LucideProps>,
{ ...childDefaultAttributes, ...customAttrs, ...attrs } as LucideProps,
);
}),
...((Array.isArray(children) ? children : [children]) || []),
],
);
},
iconNode,
...props,
}),
);
Component.displayName = `${iconName}`;
Component.displayName = toPascalCase(iconName);
return Component;
};

View File

@@ -2,6 +2,7 @@ export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export * from './context';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,32 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
`;

View File

@@ -1,60 +1,62 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewbox="0 0 24 24"
fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
<svg
data-testid="grid-icon"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<rect fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
width="18"
height="18"
x="3"
y="3"
rx="2"
>
</rect>
<path fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
d="M3 9h18"
>
</path>
<path fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
d="M3 15h18"
>
</path>
<path fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
d="M9 3v18"
>
</path>
<path fill="none"
stroke="red"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
d="M15 3v18"
>
</path>
<rect
fill="none"
height="18"
rx="2"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
width="18"
x="3"
y="3"
/>
<path
d="M3 9h18"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
/>
<path
d="M3 15h18"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
/>
<path
d="M9 3v18"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
/>
<path
d="M15 3v18"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
/>
</svg>
`;

View File

@@ -0,0 +1,80 @@
import { cleanup, render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { House, LucideProvider } from '../src/lucide-react-native';
vi.mock('react-native-svg');
describe('Using LucideProvider', () => {
it('should render the icon with LucideProvider', () => {
cleanup();
const { container } = render(
<LucideProvider>
<House />
</LucideProvider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
cleanup();
const testId = 'house-icon';
const { getByTestId } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House testID={testId} />
</LucideProvider>,
);
const IconComponent = getByTestId(testId);
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
cleanup();
const { container } = render(
<LucideProvider
size={48}
color="red"
absoluteStrokeWidth
>
<House />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it("should override the provider's global props when passing props to the icon", () => {
cleanup();
const { container } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House
size={32}
color="blue"
strokeWidth={3}
/>
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '32');
expect(IconComponent).toHaveAttribute('height', '32');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '3');
});
});

View File

@@ -14,49 +14,53 @@ describe('Using lucide icon components', () => {
});
it('should adjust the size, stroke color and stroke width', () => {
const { container } = render(
const testId = 'grid-icon';
const { getByTestId } = render(
<Grid
size={48}
stroke="red"
strokeWidth={4}
testID={testId}
/>,
);
const SVGElement = container.firstElementChild;
const GridIcon = getByTestId(testId);
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(GridIcon).toHaveAttribute('stroke', 'red');
expect(GridIcon).toHaveAttribute('width', '48');
expect(GridIcon).toHaveAttribute('height', '48');
expect(GridIcon).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot();
expect(GridIcon).toMatchSnapshot();
});
it('should render the alias icon', () => {
const testId = 'pen-icon';
const { container } = render(
const penTestId = 'pen-icon';
const { getByTestId: getByTestId1 } = render(
<Pen
data-testid={testId}
testID={penTestId}
size={48}
stroke="red"
strokeWidth={4}
/>,
);
const PenIconRenderedHTML = container.innerHTML;
const penIcon = getByTestId1(penTestId);
cleanup();
const { container: Edit2Container } = render(
const { getByTestId: getByTestId2 } = render(
<Edit2
data-testid={testId}
testID={penTestId}
size={48}
stroke="red"
strokeWidth={4}
/>,
);
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
const edit2Icon = getByTestId2(penTestId);
expect(penIcon).toStrictEqual(edit2Icon);
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
@@ -83,8 +87,8 @@ describe('Using lucide icon components', () => {
const childId = 'child';
const { container, getByTestId } = render(
<Grid data-testid={testId}>
<Grid data-testid={childId} />
<Grid testID={testId}>
<Grid testID={childId} />
</Grid>,
);
const { children } = container.firstElementChild ?? {};
@@ -100,9 +104,9 @@ describe('Using lucide icon components', () => {
const childId2 = 'child2';
const { container, getByTestId } = render(
<Grid data-testid={testId}>
<Grid data-testid={childId1} />
<Grid data-testid={childId2} />
<Grid testID={testId}>
<Grid testID={childId1} />
<Grid testID={childId2} />
</Grid>,
);
const { children } = getByTestId(testId) as unknown as { children: HTMLCollection };
@@ -123,7 +127,7 @@ describe('Using lucide icon components', () => {
const { container, getByTestId } = render(
<Grid
data-testid={testId}
testID={testId}
fill={fill}
color={color}
strokeWidth={strokeWidth}

View File

@@ -55,7 +55,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^14.1.2",
"@types/react": "^18.2.37",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^4.6.0",
"jest-serializer-html": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",

View File

@@ -2,6 +2,7 @@ import { createElement, forwardRef } from 'react';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses, hasA11yProp } from '@lucide/shared';
import { useLucideContext } from './context';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
@@ -24,28 +25,32 @@ interface IconComponentProps extends LucideProps {
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
className = '',
children,
iconNode,
...rest
},
{ color, size, strokeWidth, absoluteStrokeWidth, className = '', children, iconNode, ...rest },
ref,
) =>
createElement(
) => {
const {
size: contextSize = 24,
strokeWidth: contextStrokeWidth = 2,
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
color: contextColor = 'currentColor',
className: contextClass = '',
} = useLucideContext() ?? {};
const calculatedStrokeWidth =
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
: strokeWidth ?? contextStrokeWidth;
return createElement(
'svg',
{
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
className: mergeClasses('lucide', className),
width: size ?? contextSize ?? defaultAttributes.width,
height: size ?? contextSize ?? defaultAttributes.height,
stroke: color ?? contextColor,
strokeWidth: calculatedStrokeWidth,
className: mergeClasses('lucide', contextClass, className),
...(!children && !hasA11yProp(rest) && { 'aria-hidden': 'true' }),
...rest,
},
@@ -53,7 +58,8 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
),
);
},
);
export default Icon;

View File

@@ -0,0 +1,40 @@
import { createContext, type ReactNode, useContext, useMemo } from 'react';
import { LucideProps } from './types';
type LucideConfig = {
size: number;
color: string;
strokeWidth: number;
absoluteStrokeWidth: boolean;
className: string;
};
const LucideContext = createContext<LucideProps>({});
type LucideProviderProps = {
children: ReactNode;
} & Partial<LucideConfig>;
export function LucideProvider({
children,
size,
color,
strokeWidth,
absoluteStrokeWidth,
className,
}: LucideProviderProps) {
const value = useMemo(
() => ({
size,
color,
strokeWidth,
absoluteStrokeWidth,
className,
}),
[size, color, strokeWidth, absoluteStrokeWidth, className],
);
return <LucideContext.Provider value={value}>{children}</LucideContext.Provider>;
}
export const useLucideContext = () => useContext(LucideContext);

View File

@@ -2,6 +2,7 @@ export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export * from './context';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,24 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-house"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
/>
</svg>
`;

View File

@@ -0,0 +1,99 @@
import { render } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { House, LucideProvider } from '../src/lucide-react';
describe('Using LucideProvider', () => {
it('should render the icon with LucideProvider', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
>
<House />
</LucideProvider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with default props when no provider is used', () => {
const { container } = render(<House />);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'currentColor');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
absoluteStrokeWidth
>
<House />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it("should override the provider's global props when passing props to the icon", () => {
const { container } = render(
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House
size={24}
color="blue"
strokeWidth={2}
/>
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should merge className from provider and icon', () => {
const { container } = render(
<LucideProvider className="provider-class">
<House className="icon-class" />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('class', 'lucide provider-class lucide-house icon-class');
});
});

View File

@@ -62,7 +62,10 @@
"build:version": "node ./scripts/replaceVersion.mjs",
"build:bundle": "rollup -c rollup.config.mjs",
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --withAliases --separateAliasesFile --separateAliasesFileIgnore=fingerprint --aliasesFileExtension=.ts --iconFileExtension=.tsx --exportFileName=index.ts",
"typecheck": "tsc",
"typecheck:watch": "tsc -w",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {

View File

@@ -1,8 +1,9 @@
import { For, splitProps } from 'solid-js';
import { For, splitProps, useContext } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { LucideContext } from './context';
interface IconProps {
name?: string;
@@ -21,28 +22,40 @@ const Icon = (props: LucideProps & IconProps) => {
'absoluteStrokeWidth',
]);
const globalProps = useContext(LucideContext);
return (
<svg
{...defaultAttributes}
width={localProps.size ?? defaultAttributes.width}
height={localProps.size ?? defaultAttributes.height}
stroke={localProps.color ?? defaultAttributes.stroke}
width={localProps.size ?? globalProps.size ?? defaultAttributes.width}
height={localProps.size ?? globalProps.size ?? defaultAttributes.height}
stroke={localProps.color ?? globalProps.color ?? defaultAttributes.stroke}
stroke-width={
localProps.absoluteStrokeWidth
? (Number(localProps.strokeWidth ?? defaultAttributes['stroke-width']) * 24) /
Number(localProps.size)
: Number(localProps.strokeWidth ?? defaultAttributes['stroke-width'])
(localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth) === true
? (Number(
localProps.strokeWidth ??
globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
) *
24) /
Number(localProps.size ?? globalProps.size)
: Number(
localProps.strokeWidth ??
globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
)
}
class={mergeClasses(
'lucide',
'lucide-icon',
globalProps.class,
...(localProps.name != null
? [
`lucide-${toKebabCase(toPascalCase(localProps.name))}`,
`lucide-${toKebabCase(localProps.name)}`,
]
: []),
localProps.class != null ? localProps.class : '',
localProps.class,
)}
{...rest}
>

View File

@@ -0,0 +1,36 @@
import { createContext, splitProps, type JSXElement } from 'solid-js';
export const LucideContext = createContext<{
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}>({
size: 24,
color: 'currentColor',
strokeWidth: 2,
absoluteStrokeWidth: false,
class: '',
});
interface LucideProviderProps {
children: JSXElement;
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export function LucideProvider(props: LucideProviderProps) {
const [value, rest] = splitProps(props, [
'size',
'color',
'strokeWidth',
'absoluteStrokeWidth',
'class',
]);
return <LucideContext.Provider value={value}>{rest.children}</LucideContext.Provider>;
}

View File

@@ -2,5 +2,6 @@ export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export * from './context';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,25 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
class="lucide lucide-icon lucide-house"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
key="5wwlr5"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
key="r6nss1"
/>
</svg>
`;

View File

@@ -0,0 +1,102 @@
import { render } from '@solidjs/testing-library';
import { describe, expect, it } from 'vitest';
import { House, LucideProvider } from '../src/lucide-solid';
describe('Using LucideProvider', () => {
it('should render the icon with LucideProvider', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
>
<House />
</LucideProvider>
));
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with default props when no provider is used', () => {
const { container } = render(() => <House />);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'currentColor');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
absoluteStrokeWidth
>
<House />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it("should override the provider's global props when passing props to the icon", () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House
size={24}
color="blue"
strokeWidth={2}
/>
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should merge className from provider and icon', () => {
const { container } = render(() => (
<LucideProvider class="provider-class">
<House class="icon-class" />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute(
'class',
'lucide lucide-icon provider-class lucide-house icon-class',
);
});
});

View File

@@ -1,6 +1,7 @@
import base64SVG from '@lucide/build-icons/utils/base64SVG';
import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate';
export default async ({
export default defineExportTemplate(async({
componentName,
iconName,
children,
@@ -13,6 +14,9 @@ export default async ({
return `
import createLucideIcon from '../createLucideIcon';
import { IconNode } from '../types';
export const __iconNode: IconNode = ${JSON.stringify(children)}
/**
* @component @name ${componentName}
@@ -25,8 +29,8 @@ import createLucideIcon from '../createLucideIcon';
* @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(children)});
const ${componentName} = createLucideIcon('${iconName}', __iconNode);
export default ${componentName};
`;
};
});

View File

@@ -1,17 +1,24 @@
<script lang="ts">
import defaultAttributes from './defaultAttributes.js';
import type { IconProps } from './types.js';
import { getLucideContext } from './context.js';
const globalProps = getLucideContext() ?? {};
const {
name,
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth = false,
color = globalProps.color ?? 'currentColor',
size = globalProps.size ?? 24,
strokeWidth = globalProps.strokeWidth ?? 2,
absoluteStrokeWidth = globalProps.absoluteStrokeWidth ?? false,
iconNode = [],
children,
...props
}: IconProps = $props();
const calculatedStrokeWidth = $derived(
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
);
</script>
<svg
@@ -20,8 +27,8 @@
width={size}
height={size}
stroke={color}
stroke-width={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
class={['lucide-icon lucide', name && `lucide-${name}`, props.class]}
stroke-width={calculatedStrokeWidth}
class={['lucide-icon lucide', globalProps.class, name && `lucide-${name}`, props.class]}
>
{#each iconNode as [tag, attrs]}
<svelte:element

View File

@@ -0,0 +1,16 @@
import { getContext, setContext } from 'svelte';
const LucideContext = Symbol('lucide-context');
export interface LucideGlobalContext {
color?: string;
size?: number;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export const setLucideProps = (globalProps: LucideGlobalContext) =>
setContext(LucideContext, globalProps);
export const getLucideContext = () => getContext<LucideGlobalContext>(LucideContext);

View File

@@ -4,3 +4,4 @@ export * from './aliases/index.js';
export { default as defaultAttributes } from './defaultAttributes.js';
export * from './types.js';
export { default as Icon } from './Icon.svelte';
export * from './context.js';

View File

@@ -0,0 +1,15 @@
<script lang="ts">
import Smile from '../src/icons/smile.svelte';
import { setLucideProps } from '../src/lucide-svelte.js';
setLucideProps({
size: 32,
color: 'red',
strokeWidth: 1,
class: 'provider-class',
});
</script>
<Smile aria-label="smile">
<text>Test</text>
</Smile>

View File

@@ -126,7 +126,6 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
data-testid="smile-icon"
class="lucide-icon lucide lucide-smile"
>
<circle cx="12"

View File

@@ -2,6 +2,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, cleanup } from '@testing-library/svelte';
import { Smile, Pen, Edit2 } from '../src/lucide-svelte.js';
import TestSlots from './TestSlots.svelte';
import ContextWrapper from './ContextWrapper.svelte';
describe('Using lucide icon components', () => {
afterEach(() => cleanup());
@@ -12,11 +13,9 @@ describe('Using lucide icon components', () => {
it('should adjust the size, stroke color and stroke width', () => {
const { container } = render(Smile, {
props: {
size: 48,
color: 'red',
strokeWidth: 4,
},
size: 48,
color: 'red',
strokeWidth: 4,
});
expect(container).toMatchSnapshot();
@@ -24,30 +23,27 @@ describe('Using lucide icon components', () => {
it('should add a class to the element', () => {
const testClass = 'my-icon';
render(Smile, {
props: {
class: testClass,
},
const { container } = render(Smile, {
class: testClass,
});
const [icon] = document.getElementsByClassName(testClass);
const IconComponent = container.firstElementChild;
expect(icon).toBeInTheDocument();
expect(icon).toMatchSnapshot();
expect(icon).toHaveClass(testClass);
expect(icon).toHaveClass('lucide');
expect(icon).toHaveClass('lucide-smile');
expect(IconComponent).toBeInTheDocument();
expect(IconComponent).toMatchSnapshot();
expect(IconComponent).toHaveClass(testClass);
expect(IconComponent).toHaveClass('lucide');
expect(IconComponent).toHaveClass('lucide-smile');
});
it('should add a style attribute to the element', () => {
render(Smile, {
props: {
style: 'position: absolute;',
},
const { container } = render(Smile, {
style: 'position: absolute;',
});
const [icon] = document.getElementsByClassName('lucide');
expect(icon.getAttribute('style')).toContain('position: absolute');
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('style', 'position: absolute;');
});
it('should render an icon slot', () => {
@@ -71,22 +67,30 @@ describe('Using lucide icon components', () => {
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'smile-icon';
const { container, getByTestId } = render(Smile, {
'data-testid': testId,
const { container } = render(Smile, {
color: 'red',
size: 48,
absoluteStrokeWidth: true,
});
const { attributes } = getByTestId(testId) as unknown as {
attributes: Record<string, { value: string }>;
};
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('1');
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '1');
expect(container.innerHTML).toMatchSnapshot();
});
it('should use context values from the global set properties', () => {
const { container } = render(ContextWrapper);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '32');
expect(IconComponent).toHaveAttribute('height', '32');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
});

View File

@@ -23,10 +23,10 @@ export const __iconNode: IconNode = ${JSON.stringify(children)}
* @description Lucide SVG icon component, renders SVG Element with children.
*
* @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName}
* @see https://lucide.dev/guide/packages/lucide-react - Documentation
* @see https://lucide.dev/guide/packages/lucide-vue-next - Documentation
*
* @param {Object} props - Lucide icons props and any valid SVG attribute
* @returns {JSX.Element} JSX Element
* @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = createLucideIcon('${iconName}', __iconNode);

View File

@@ -1,7 +1,8 @@
import { type FunctionalComponent, h } from 'vue';
import { mergeClasses, toKebabCase, toPascalCase, isEmptyString } from '@lucide/shared';
import { computed, type FunctionalComponent, h } from 'vue';
import { isEmptyString, mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { useLucideProps } from './context';
interface IconProps {
iconNode: IconNode;
@@ -16,32 +17,55 @@ const Icon: FunctionalComponent<LucideProps & IconProps> = (
'absolute-stroke-width': absoluteStrokeWidthKebabCase,
strokeWidth,
'stroke-width': strokeWidthKebabCase,
size = defaultAttributes.width,
color = defaultAttributes.stroke,
size,
color,
...props
},
{ slots },
) => {
const {
size: contextSize,
color: contextColor,
strokeWidth: contextStrokeWidth = 2,
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
class: contextClass = '',
} = useLucideProps();
const calculatedStrokeWidth = computed(() => {
const isAbsoluteStrokeWidth =
isEmptyString(absoluteStrokeWidth) ||
isEmptyString(absoluteStrokeWidthKebabCase) ||
absoluteStrokeWidth === true ||
absoluteStrokeWidthKebabCase === true ||
contextAbsoluteStrokeWidth === true;
const strokeWidthValue =
strokeWidth ||
strokeWidthKebabCase ||
contextStrokeWidth ||
defaultAttributes['stroke-width'];
if (isAbsoluteStrokeWidth) {
return (
(Number(strokeWidthValue) * 24) / Number(size ?? contextSize ?? defaultAttributes.width)
);
}
return strokeWidthValue;
});
return h(
'svg',
{
...defaultAttributes,
...props,
width: size,
height: size,
stroke: color,
'stroke-width':
isEmptyString(absoluteStrokeWidth) ||
isEmptyString(absoluteStrokeWidthKebabCase) ||
absoluteStrokeWidth === true ||
absoluteStrokeWidthKebabCase === true
? (Number(strokeWidth || strokeWidthKebabCase || defaultAttributes['stroke-width']) *
24) /
Number(size)
: strokeWidth || strokeWidthKebabCase || defaultAttributes['stroke-width'],
width: size ?? contextSize ?? defaultAttributes.width,
height: size ?? contextSize ?? defaultAttributes.height,
stroke: color ?? contextColor ?? defaultAttributes.stroke,
'stroke-width': calculatedStrokeWidth.value,
class: mergeClasses(
'lucide',
props.class,
contextClass,
...(name
? [`lucide-${toKebabCase(toPascalCase(name))}-icon`, `lucide-${toKebabCase(name)}`]
: ['lucide-icon']),

View File

@@ -0,0 +1,19 @@
import { provide, inject } from 'vue';
export const LUCIDE_CONTEXT = Symbol('lucide-icons');
interface LucideIconsContext {
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export function setLucideProps(props: LucideIconsContext) {
return provide(LUCIDE_CONTEXT, props);
}
export function useLucideProps() {
return inject<LucideIconsContext>(LUCIDE_CONTEXT, {});
}

View File

@@ -1,10 +1,7 @@
import { h } from 'vue';
import type { FunctionalComponent } from 'vue';
import { IconNode, LucideProps } from './types';
import { IconNode, LucideIcon } from './types';
import Icon from './Icon';
// Create interface extending SVGAttributes
/**
* Create a Lucide icon component
* @param {string} iconName
@@ -12,7 +9,7 @@ import Icon from './Icon';
* @returns {FunctionalComponent} LucideIcon
*/
const createLucideIcon =
(iconName: string, iconNode: IconNode): FunctionalComponent<LucideProps> =>
(iconName: string, iconNode: IconNode): LucideIcon =>
(props, { slots, attrs }) =>
h(
Icon,

View File

@@ -4,10 +4,8 @@ export interface LucideProps extends Partial<SVGAttributes> {
size?: 24 | number;
strokeWidth?: number | string;
absoluteStrokeWidth?: boolean;
'absolute-stroke-width'?: boolean;
}
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type LucideIcon = FunctionalComponent<LucideProps>;
// Legacy exports
export type SVGProps = LucideProps;

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { setLucideProps } from '../src/context';
import { House } from '../src/lucide-vue';
const props = defineProps<{
size?: number;
color?: string;
strokeWidth?: number;
}>();
setLucideProps({
size: 48,
color: 'red',
strokeWidth: 4,
class: 'provider-class',
});
</script>
<template>
<House v-bind="props" />
</template>

View File

@@ -0,0 +1,23 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using lucide icon context > should render the icon with LucideProvider 1`] = `
<svg
class="lucide provider-class lucide-house-icon lucide-house"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="4"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
/>
</svg>
`;

View File

@@ -3,7 +3,7 @@
exports[`Using lucide icon components > should add a class to the element 1`] = `
<div>
<svg
class="lucide my-icon lucide-smile-icon lucide-smile my-icon"
class="lucide lucide-smile-icon lucide-smile my-icon"
fill="none"
height="24"
stroke="currentColor"

View File

@@ -0,0 +1,65 @@
import { render } from '@testing-library/vue';
import { describe, expect, it } from 'vitest';
// @ts-ignore
import ContextWrapper from './ContextWrapper.vue';
describe('Using lucide icon context', () => {
it('should render the icon with LucideProvider', () => {
const { container } = render(ContextWrapper);
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
const { container } = render(ContextWrapper);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
const { container } = render(ContextWrapper);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it("should override the provider's global props when passing props to the icon", () => {
const { container } = render(ContextWrapper, {
props: {
strokeWidth: 1,
color: 'blue',
size: 24,
},
});
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it('should merge className from provider and icon', () => {
const { container } = render(ContextWrapper, {
props: {
class: 'icon-class',
},
});
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute(
'class',
'lucide provider-class lucide-house-icon lucide-house icon-class',
);
});
});

View File

@@ -85,7 +85,7 @@ describe('Using lucide icon components', () => {
const icon = container.firstElementChild;
await fireEvent.click(icon);
await fireEvent.click(icon as Element);
expect(onClick).toHaveBeenCalled();
});
@@ -153,7 +153,7 @@ describe('Using lucide icon components', () => {
props: {
size: 48,
color: 'red',
absoluteStrokeWidth: '',
absoluteStrokeWidth: '' as unknown as boolean, // Vue treats empty string as true for boolean props
},
});
@@ -170,7 +170,7 @@ describe('Using lucide icon components', () => {
size: 48,
color: 'red',
'stroke-width': '2',
'absolute-stroke-width': '',
'absolute-stroke-width': '' as unknown as boolean, // Vue treats empty string as true for boolean props
},
});

View File

@@ -13,6 +13,6 @@
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"types": ["@testing-library/jest-dom"],
"types": ["@testing-library/jest-dom", "vitest/globals"],
},
}

192
pnpm-lock.yaml generated
View File

@@ -409,7 +409,7 @@ importers:
specifier: ^7.1.0
version: 7.1.0
preact:
specifier: ^10.19.2
specifier: ^10.26.9
version: 10.27.2
rollup:
specifier: ^4.53.3
@@ -448,7 +448,7 @@ importers:
specifier: ^18.2.37
version: 18.3.27
'@vitejs/plugin-react':
specifier: ^4.4.1
specifier: ^4.6.0
version: 4.7.0(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))
jest-serializer-html:
specifier: ^7.1.0
@@ -8827,6 +8827,9 @@ packages:
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'}
is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
is-bigint@1.1.0:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'}
@@ -8835,6 +8838,10 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'}
@@ -8861,6 +8868,10 @@ packages:
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
engines: {node: '>= 0.4'}
is-date-object@1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
is-date-object@1.1.0:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'}
@@ -8918,6 +8929,9 @@ packages:
is-lambda@1.0.1:
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
is-map@2.0.2:
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
is-map@2.0.3:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
@@ -8929,6 +8943,10 @@ packages:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
is-number-object@1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'}
is-number-object@1.1.1:
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
engines: {node: '>= 0.4'}
@@ -9014,6 +9032,9 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
is-weakmap@2.0.1:
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@@ -9022,6 +9043,9 @@ packages:
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
engines: {node: '>= 0.4'}
is-weakset@2.0.2:
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
is-weakset@2.0.4:
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'}
@@ -11710,6 +11734,10 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
@@ -12883,6 +12911,46 @@ packages:
terser:
optional: true
vite@6.3.6:
resolution: {integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vite@6.4.1:
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -13190,6 +13258,9 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@@ -13198,6 +13269,9 @@ packages:
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
engines: {node: '>= 0.4'}
which-collection@1.0.1:
resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==}
which-collection@1.0.2:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'}
@@ -15704,7 +15778,7 @@ snapshots:
'@babel/parser': 7.28.5
'@babel/template': 7.27.2
'@babel/types': 7.28.5
debug: 4.4.3
debug: 4.3.5
transitivePeerDependencies:
- supports-color
@@ -18036,7 +18110,7 @@ snapshots:
'@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.3(svelte@4.2.20)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0)))(svelte@4.2.20)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))':
dependencies:
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.2.20)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))
debug: 4.4.3
debug: 4.4.0
svelte: 4.2.20
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
transitivePeerDependencies:
@@ -18045,7 +18119,7 @@ snapshots:
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)))(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))
debug: 4.4.3
debug: 4.4.1
svelte: 5.43.14
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
transitivePeerDependencies:
@@ -18187,7 +18261,7 @@ snapshots:
'@testing-library/dom@8.20.1':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/runtime': 7.27.1
'@babel/runtime': 7.26.0
'@types/aria-query': 5.0.4
aria-query: 5.1.3
chalk: 4.1.2
@@ -18198,7 +18272,7 @@ snapshots:
'@testing-library/dom@9.3.4':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/runtime': 7.27.1
'@babel/runtime': 7.26.0
'@types/aria-query': 5.0.4
aria-query: 5.1.3
chalk: 4.1.2
@@ -18654,7 +18728,7 @@ snapshots:
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
'@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
debug: 4.4.3
debug: 4.4.0
eslint: 8.57.1
ts-api-utils: 1.4.3(typescript@5.9.3)
optionalDependencies:
@@ -18700,7 +18774,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3
debug: 4.4.0
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
@@ -18863,13 +18937,13 @@ snapshots:
chai: 6.2.1
tinyrainbow: 3.0.3
'@vitest/mocker@4.0.12(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))':
'@vitest/mocker@4.0.12(vite@6.3.6(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 4.0.12
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
vite: 6.3.6(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
'@vitest/pretty-format@4.0.12':
dependencies:
@@ -19948,7 +20022,7 @@ snapshots:
'@npmcli/move-file': 1.1.2
chownr: 2.0.0
fs-minipass: 2.1.0
glob: 7.2.0
glob: 7.2.3
infer-owner: 1.0.4
lru-cache: 6.0.0
minipass: 3.3.6
@@ -20620,7 +20694,7 @@ snapshots:
deep-equal@1.1.2:
dependencies:
is-arguments: 1.1.1
is-date-object: 1.1.0
is-date-object: 1.0.5
is-regex: 1.2.1
object-is: 1.1.6
object-keys: 1.1.1
@@ -20634,7 +20708,7 @@ snapshots:
get-intrinsic: 1.3.0
is-arguments: 1.1.1
is-array-buffer: 3.0.5
is-date-object: 1.1.0
is-date-object: 1.0.5
is-regex: 1.2.1
is-shared-array-buffer: 1.0.4
isarray: 2.0.5
@@ -20642,9 +20716,9 @@ snapshots:
object-keys: 1.1.1
object.assign: 4.1.7
regexp.prototype.flags: 1.5.4
side-channel: 1.1.0
which-boxed-primitive: 1.1.1
which-collection: 1.0.2
side-channel: 1.0.6
which-boxed-primitive: 1.0.2
which-collection: 1.0.1
which-typed-array: 1.1.19
deep-is@0.1.4: {}
@@ -21089,7 +21163,7 @@ snapshots:
get-intrinsic: 1.3.0
has-symbols: 1.1.0
is-arguments: 1.1.1
is-map: 2.0.3
is-map: 2.0.2
is-set: 2.0.3
is-string: 1.1.1
isarray: 2.0.5
@@ -22467,7 +22541,7 @@ snapshots:
dependencies:
'@tootallnate/once': 1.1.2
agent-base: 6.0.2
debug: 4.4.3
debug: 4.3.3
transitivePeerDependencies:
- supports-color
@@ -22699,6 +22773,10 @@ snapshots:
has-tostringtag: 1.0.2
safe-regex-test: 1.1.0
is-bigint@1.0.4:
dependencies:
has-bigints: 1.1.0
is-bigint@1.1.0:
dependencies:
has-bigints: 1.1.0
@@ -22707,6 +22785,11 @@ snapshots:
dependencies:
binary-extensions: 2.3.0
is-boolean-object@1.1.2:
dependencies:
call-bind: 1.0.8
has-tostringtag: 1.0.2
is-boolean-object@1.2.2:
dependencies:
call-bound: 1.0.4
@@ -22734,6 +22817,10 @@ snapshots:
get-intrinsic: 1.3.0
is-typed-array: 1.1.15
is-date-object@1.0.5:
dependencies:
has-tostringtag: 1.0.2
is-date-object@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -22776,12 +22863,18 @@ snapshots:
is-lambda@1.0.1: {}
is-map@2.0.2: {}
is-map@2.0.3: {}
is-module@1.0.0: {}
is-negative-zero@2.0.3: {}
is-number-object@1.0.7:
dependencies:
has-tostringtag: 1.0.2
is-number-object@1.1.1:
dependencies:
call-bound: 1.0.4
@@ -22851,12 +22944,19 @@ snapshots:
is-unicode-supported@0.1.0: {}
is-weakmap@2.0.1: {}
is-weakmap@2.0.2: {}
is-weakref@1.1.1:
dependencies:
call-bound: 1.0.4
is-weakset@2.0.2:
dependencies:
call-bind: 1.0.8
get-intrinsic: 1.3.0
is-weakset@2.0.4:
dependencies:
call-bound: 1.0.4
@@ -23858,7 +23958,7 @@ snapshots:
metro-runtime@0.81.0:
dependencies:
'@babel/runtime': 7.27.1
'@babel/runtime': 7.26.0
flow-enums-runtime: 0.0.6
metro-runtime@0.81.5:
@@ -25785,7 +25885,7 @@ snapshots:
qs@6.13.0:
dependencies:
side-channel: 1.1.0
side-channel: 1.0.6
quansync@0.2.11: {}
@@ -26696,6 +26796,13 @@ snapshots:
object-inspect: 1.13.4
side-channel-map: 1.0.1
side-channel@1.0.6:
dependencies:
call-bind: 1.0.8
es-errors: 1.3.0
get-intrinsic: 1.3.0
object-inspect: 1.13.4
side-channel@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -26787,7 +26894,7 @@ snapshots:
socks-proxy-agent@6.2.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.3
debug: 4.3.3
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@@ -26795,7 +26902,7 @@ snapshots:
socks-proxy-agent@7.0.0:
dependencies:
agent-base: 6.0.2
debug: 4.4.3
debug: 4.3.3
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@@ -27095,7 +27202,7 @@ snapshots:
dependencies:
css: 3.0.0
debug: 4.4.3
glob: 7.2.0
glob: 7.2.3
safer-buffer: 2.1.2
sax: 1.2.4
source-map: 0.7.4
@@ -28010,6 +28117,24 @@ snapshots:
stylus: 0.56.0
terser: 5.44.1
vite@6.3.6(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.53.3
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.10.1
fsevents: 2.3.3
jiti: 2.6.1
less: 4.2.0
sass: 1.77.8
stylus: 0.56.0
terser: 5.44.1
yaml: 2.8.0
vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0):
dependencies:
esbuild: 0.25.10
@@ -28110,7 +28235,7 @@ snapshots:
vitest@4.0.12(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@20.0.3)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0):
dependencies:
'@vitest/expect': 4.0.12
'@vitest/mocker': 4.0.12(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))
'@vitest/mocker': 4.0.12(vite@6.3.6(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(terser@5.44.1)(yaml@2.8.0))
'@vitest/pretty-format': 4.0.12
'@vitest/runner': 4.0.12
'@vitest/snapshot': 4.0.12
@@ -28127,7 +28252,7 @@ snapshots:
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
vite: 6.3.6(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
@@ -28329,6 +28454,14 @@ snapshots:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-boxed-primitive@1.0.2:
dependencies:
is-bigint: 1.0.4
is-boolean-object: 1.1.2
is-number-object: 1.0.7
is-string: 1.1.1
is-symbol: 1.1.1
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@@ -28353,6 +28486,13 @@ snapshots:
which-collection: 1.0.2
which-typed-array: 1.1.19
which-collection@1.0.1:
dependencies:
is-map: 2.0.2
is-set: 2.0.3
is-weakmap: 2.0.1
is-weakset: 2.0.2
which-collection@1.0.2:
dependencies:
is-map: 2.0.3