feat(context-providers): Adding Context providers (#3315)

* Add context provider for svelte package

* Add context provider for react

* Remove old vue 2 package

* Add @lucide/vue package

* Remove old vue 2 doc

* Update docs

* Adjust export template

* Add context provider to solid package

* Fix context

* Implement context providers

* Add context provider to preact

* Adjust vue package!

* Fix tests

* Format code

* Add context provider for Vue

* Improve memoization

* Update packages/lucide-react-native/tests/context.spec.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply fixes

* Cleanup

* Formatting

* Move on

* Update packages/lucide-react/tests/context.spec.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/lucide-preact/tests/context.spec.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/vue/tests/context.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove x

* update export template

* apply feedback

* fix export template

* Fixes types and tests

* fix tests

* apply feedback

* Remove class

* Formatting

* Update packages/svelte/src/context.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove fill form context providers

* formatting

* Add classname to context provider

* Apply feedback

* Format code

* Rename files

* Rename tsx to ts

* Format file

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Eric Fennis
2026-01-02 09:47:29 +01:00
committed by GitHub
parent 5fab4e7bc8
commit 0c6dfeb64c
47 changed files with 1277 additions and 269 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: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", "build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run", "test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "devDependencies": {
@@ -47,7 +48,7 @@
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/preact": "^3.2.3", "@testing-library/preact": "^3.2.3",
"jest-serializer-html": "^7.1.0", "jest-serializer-html": "^7.1.0",
"preact": "^10.19.2", "preact": "^10.26.9",
"rollup": "^4.53.3", "rollup": "^4.53.3",
"rollup-plugin-dts": "^6.2.3", "rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",

View File

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

View File

@@ -0,0 +1,49 @@
import { createContext, h, 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 h(LucideContext.Provider, { value }, children);
}
export const useLucideContext = () => useContext(LucideContext);

View File

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

View File

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

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

View File

@@ -2,9 +2,12 @@ import { createElement, forwardRef, type FunctionComponent } from 'react';
import * as NativeSvg from 'react-native-svg'; import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes'; import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import { IconNode, LucideProps } from './types'; import { IconNode, LucideProps } from './types';
import { useLucideContext } from './context';
interface IconComponentProps extends LucideProps { interface IconComponentProps extends LucideProps {
iconNode: IconNode; iconNode: IconNode;
testID?: string;
className?: string;
} }
/** /**
@@ -25,20 +28,33 @@ interface IconComponentProps extends LucideProps {
const Icon = forwardRef<SVGSVGElement, IconComponentProps>( const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
( (
{ {
color = 'currentColor', color,
size = 24, size,
strokeWidth = 2, strokeWidth,
absoluteStrokeWidth, absoluteStrokeWidth,
children, children,
iconNode, iconNode,
className, className,
testID,
...rest ...rest
}, },
ref, 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 = { const customAttrs = {
stroke: color, stroke: color ?? contextColor ?? defaultAttributes.stroke,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth, strokeWidth: calculatedStrokeWidth,
...rest, ...rest,
}; };
@@ -47,9 +63,10 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
{ {
ref, ref,
...defaultAttributes, ...defaultAttributes,
width: size ?? contextSize ?? defaultAttributes.width,
height: size ?? contextSize ?? defaultAttributes.height,
'data-testid': testID,
className, className,
width: size,
height: size,
...customAttrs, ...customAttrs,
}, },
[ [

View File

@@ -0,0 +1,43 @@
import { createContext, createElement, 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 createElement(LucideContext.Provider, { value }, children);
}
export const useLucideContext = () => useContext(LucideContext);

View File

@@ -1,55 +1,24 @@
import { forwardRef, createElement, FunctionComponent } from 'react'; import { forwardRef, createElement } from 'react';
import * as NativeSvg from 'react-native-svg'; import { IconNode, LucideProps } from './types';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes'; import { toPascalCase } from '@lucide/shared';
import { IconNode, LucideIcon, LucideProps } from './types'; import Icon from './Icon';
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => { /**
const Component = forwardRef( * Create a Lucide icon component
( * @param {string} iconName
{ * @param {array} iconNode
color = 'currentColor', * @returns {ForwardRefExoticComponent} LucideIcon
size = 24, */
strokeWidth = 2, const createLucideIcon = (iconName: string, iconNode: IconNode) => {
absoluteStrokeWidth, const Component = forwardRef<SVGSVGElement, LucideProps>((props, ref) =>
children, createElement(Icon, {
'data-testid': dataTestId,
...rest
}: LucideProps,
ref, ref,
) => { iconNode,
const customAttrs = { ...props,
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]) || []),
],
);
},
); );
Component.displayName = `${iconName}`; Component.displayName = toPascalCase(iconName);
return Component; return Component;
}; };

View File

@@ -1,6 +1,7 @@
export * from './icons'; export * from './icons';
export * from './aliases'; export * from './aliases';
export * from './types'; export * from './types';
export * from './context';
export { default as createLucideIcon } from './createLucideIcon'; export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon'; 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 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = ` exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
<svg xmlns="http://www.w3.org/2000/svg" <svg
width="48" data-testid="grid-icon"
height="48" fill="none"
viewbox="0 0 24 24" height="48"
fill="none" stroke="red"
stroke="red" stroke-linecap="round"
stroke-width="4" stroke-linejoin="round"
stroke-linecap="round" stroke-width="4"
stroke-linejoin="round" viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
> >
<rect fill="none" <rect
stroke="red" fill="none"
stroke-width="4" height="18"
stroke-linecap="round" rx="2"
stroke-linejoin="round" stroke="red"
width="18" stroke-linecap="round"
height="18" stroke-linejoin="round"
x="3" stroke-width="4"
y="3" width="18"
rx="2" x="3"
> y="3"
</rect> />
<path fill="none" <path
stroke="red" d="M3 9h18"
stroke-width="4" fill="none"
stroke-linecap="round" stroke="red"
stroke-linejoin="round" stroke-linecap="round"
d="M3 9h18" stroke-linejoin="round"
> stroke-width="4"
</path> />
<path fill="none" <path
stroke="red" d="M3 15h18"
stroke-width="4" fill="none"
stroke-linecap="round" stroke="red"
stroke-linejoin="round" stroke-linecap="round"
d="M3 15h18" stroke-linejoin="round"
> stroke-width="4"
</path> />
<path fill="none" <path
stroke="red" d="M9 3v18"
stroke-width="4" fill="none"
stroke-linecap="round" stroke="red"
stroke-linejoin="round" stroke-linecap="round"
d="M9 3v18" stroke-linejoin="round"
> stroke-width="4"
</path> />
<path fill="none" <path
stroke="red" d="M15 3v18"
stroke-width="4" fill="none"
stroke-linecap="round" stroke="red"
stroke-linejoin="round" stroke-linecap="round"
d="M15 3v18" stroke-linejoin="round"
> stroke-width="4"
</path> />
</svg> </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', () => { it('should adjust the size, stroke color and stroke width', () => {
const { container } = render( const testId = 'grid-icon';
const { getByTestId } = render(
<Grid <Grid
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
testID={testId}
/>, />,
); );
const SVGElement = container.firstElementChild; const GridIcon = getByTestId(testId);
expect(SVGElement).toHaveAttribute('stroke', 'red'); expect(GridIcon).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48'); expect(GridIcon).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48'); expect(GridIcon).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4'); expect(GridIcon).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot(); expect(GridIcon).toMatchSnapshot();
}); });
it('should render the alias icon', () => { it('should render the alias icon', () => {
const testId = 'pen-icon'; const penTestId = 'pen-icon';
const { container } = render( const { getByTestId: getByTestId1 } = render(
<Pen <Pen
data-testid={testId} testID={penTestId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
/>, />,
); );
const PenIconRenderedHTML = container.innerHTML; const penIcon = getByTestId1(penTestId);
cleanup(); cleanup();
const { container: Edit2Container } = render( const { getByTestId: getByTestId2 } = render(
<Edit2 <Edit2
data-testid={testId} testID={penTestId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} 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', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
@@ -83,8 +87,8 @@ describe('Using lucide icon components', () => {
const childId = 'child'; const childId = 'child';
const { container, getByTestId } = render( const { container, getByTestId } = render(
<Grid data-testid={testId}> <Grid testID={testId}>
<Grid data-testid={childId} /> <Grid testID={childId} />
</Grid>, </Grid>,
); );
const { children } = container.firstElementChild ?? {}; const { children } = container.firstElementChild ?? {};
@@ -100,9 +104,9 @@ describe('Using lucide icon components', () => {
const childId2 = 'child2'; const childId2 = 'child2';
const { container, getByTestId } = render( const { container, getByTestId } = render(
<Grid data-testid={testId}> <Grid testID={testId}>
<Grid data-testid={childId1} /> <Grid testID={childId1} />
<Grid data-testid={childId2} /> <Grid testID={childId2} />
</Grid>, </Grid>,
); );
const { children } = getByTestId(testId) as unknown as { children: HTMLCollection }; const { children } = getByTestId(testId) as unknown as { children: HTMLCollection };
@@ -123,7 +127,7 @@ describe('Using lucide icon components', () => {
const { container, getByTestId } = render( const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId} testID={testId}
fill={fill} fill={fill}
color={color} color={color}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
import { createContext, createElement, 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 createElement(LucideContext.Provider, { value }, children);
}
export const useLucideContext = () => useContext(LucideContext);

View File

@@ -2,6 +2,7 @@ export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export * from './types'; export * from './types';
export * from './context';
export { default as createLucideIcon } from './createLucideIcon'; export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon'; 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:version": "node ./scripts/replaceVersion.mjs",
"build:bundle": "rollup -c rollup.config.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", "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": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "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 { Dynamic } from 'solid-js/web';
import defaultAttributes from './defaultAttributes'; import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types'; import { IconNode, LucideProps } from './types';
import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared'; import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { LucideContext } from './context';
interface IconProps { interface IconProps {
name?: string; name?: string;
@@ -21,28 +22,40 @@ const Icon = (props: LucideProps & IconProps) => {
'absoluteStrokeWidth', 'absoluteStrokeWidth',
]); ]);
const globalProps = useContext(LucideContext);
return ( return (
<svg <svg
{...defaultAttributes} {...defaultAttributes}
width={localProps.size ?? defaultAttributes.width} width={localProps.size ?? globalProps.size ?? defaultAttributes.width}
height={localProps.size ?? defaultAttributes.height} height={localProps.size ?? globalProps.size ?? defaultAttributes.height}
stroke={localProps.color ?? defaultAttributes.stroke} stroke={localProps.color ?? globalProps.color ?? defaultAttributes.stroke}
stroke-width={ stroke-width={
localProps.absoluteStrokeWidth (localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth) === true
? (Number(localProps.strokeWidth ?? defaultAttributes['stroke-width']) * 24) / ? (Number(
Number(localProps.size) localProps.strokeWidth ??
: Number(localProps.strokeWidth ?? defaultAttributes['stroke-width']) globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
) *
24) /
Number(localProps.size ?? globalProps.size)
: Number(
localProps.strokeWidth ??
globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
)
} }
class={mergeClasses( class={mergeClasses(
'lucide', 'lucide',
'lucide-icon', 'lucide-icon',
globalProps.class,
...(localProps.name != null ...(localProps.name != null
? [ ? [
`lucide-${toKebabCase(toPascalCase(localProps.name))}`, `lucide-${toKebabCase(toPascalCase(localProps.name))}`,
`lucide-${toKebabCase(localProps.name)}`, `lucide-${toKebabCase(localProps.name)}`,
] ]
: []), : []),
localProps.class != null ? localProps.class : '', localProps.class,
)} )}
{...rest} {...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 * as icons from './icons';
export * from './aliases'; export * from './aliases';
export * from './types'; export * from './types';
export * from './context';
export { default as Icon } from './Icon'; 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 base64SVG from '@lucide/build-icons/utils/base64SVG';
import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate';
export default async ({ export default defineExportTemplate(async({
componentName, componentName,
iconName, iconName,
children, children,
@@ -13,6 +14,9 @@ export default async ({
return ` return `
import createLucideIcon from '../createLucideIcon'; import createLucideIcon from '../createLucideIcon';
import { IconNode } from '../types';
export const __iconNode: IconNode = ${JSON.stringify(children)}
/** /**
* @component @name ${componentName} * @component @name ${componentName}
@@ -25,8 +29,8 @@ import createLucideIcon from '../createLucideIcon';
* @returns {FunctionalComponent} Vue component * @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(children)}); const ${componentName} = createLucideIcon('${iconName}', __iconNode);
export default ${componentName}; export default ${componentName};
`; `;
}; });

View File

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

View File

@@ -2,6 +2,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, cleanup } from '@testing-library/svelte'; import { render, cleanup } from '@testing-library/svelte';
import { Smile, Pen, Edit2 } from '../src/lucide-svelte.js'; import { Smile, Pen, Edit2 } from '../src/lucide-svelte.js';
import TestSlots from './TestSlots.svelte'; import TestSlots from './TestSlots.svelte';
import ContextWrapper from './ContextWrapper.svelte';
describe('Using lucide icon components', () => { describe('Using lucide icon components', () => {
afterEach(() => cleanup()); afterEach(() => cleanup());
@@ -12,11 +13,9 @@ describe('Using lucide icon components', () => {
it('should adjust the size, stroke color and stroke width', () => { it('should adjust the size, stroke color and stroke width', () => {
const { container } = render(Smile, { const { container } = render(Smile, {
props: { size: 48,
size: 48, color: 'red',
color: 'red', strokeWidth: 4,
strokeWidth: 4,
},
}); });
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
@@ -24,30 +23,27 @@ describe('Using lucide icon components', () => {
it('should add a class to the element', () => { it('should add a class to the element', () => {
const testClass = 'my-icon'; const testClass = 'my-icon';
render(Smile, { const { container } = render(Smile, {
props: { class: testClass,
class: testClass,
},
}); });
const [icon] = document.getElementsByClassName(testClass); const IconComponent = container.firstElementChild;
expect(icon).toBeInTheDocument(); expect(IconComponent).toBeInTheDocument();
expect(icon).toMatchSnapshot(); expect(IconComponent).toMatchSnapshot();
expect(icon).toHaveClass(testClass); expect(IconComponent).toHaveClass(testClass);
expect(icon).toHaveClass('lucide'); expect(IconComponent).toHaveClass('lucide');
expect(icon).toHaveClass('lucide-smile'); expect(IconComponent).toHaveClass('lucide-smile');
}); });
it('should add a style attribute to the element', () => { it('should add a style attribute to the element', () => {
render(Smile, { const { container } = render(Smile, {
props: { style: 'position: absolute;',
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', () => { 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', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'smile-icon'; const { container } = render(Smile, {
const { container, getByTestId } = render(Smile, {
'data-testid': testId,
color: 'red', color: 'red',
size: 48, size: 48,
absoluteStrokeWidth: true, absoluteStrokeWidth: true,
}); });
const { attributes } = getByTestId(testId) as unknown as { const IconComponent = container.firstElementChild;
attributes: Record<string, { value: string }>;
}; expect(IconComponent).toHaveAttribute('width', '48');
expect(attributes.stroke.value).toBe('red'); expect(IconComponent).toHaveAttribute('height', '48');
expect(attributes.width.value).toBe('48'); expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(attributes.height.value).toBe('48'); expect(IconComponent).toHaveAttribute('stroke-width', '1');
expect(attributes['stroke-width'].value).toBe('1');
expect(container.innerHTML).toMatchSnapshot(); 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. * @description Lucide SVG icon component, renders SVG Element with children.
* *
* @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName} * @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 * @param {Object} props - Lucide icons props and any valid SVG attribute
* @returns {JSX.Element} JSX Element * @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = createLucideIcon('${iconName}', __iconNode); const ${componentName} = createLucideIcon('${iconName}', __iconNode);

View File

@@ -1,7 +1,8 @@
import { type FunctionalComponent, h } from 'vue'; import { computed, type FunctionalComponent, h } from 'vue';
import { mergeClasses, toKebabCase, toPascalCase, isEmptyString } from '@lucide/shared'; import { isEmptyString, mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import defaultAttributes from './defaultAttributes'; import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types'; import { IconNode, LucideProps } from './types';
import { useLucideProps } from './context';
interface IconProps { interface IconProps {
iconNode: IconNode; iconNode: IconNode;
@@ -16,32 +17,55 @@ const Icon: FunctionalComponent<LucideProps & IconProps> = (
'absolute-stroke-width': absoluteStrokeWidthKebabCase, 'absolute-stroke-width': absoluteStrokeWidthKebabCase,
strokeWidth, strokeWidth,
'stroke-width': strokeWidthKebabCase, 'stroke-width': strokeWidthKebabCase,
size = defaultAttributes.width, size,
color = defaultAttributes.stroke, color,
...props ...props
}, },
{ slots }, { 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( return h(
'svg', 'svg',
{ {
...defaultAttributes, ...defaultAttributes,
...props, ...props,
width: size, width: size ?? contextSize ?? defaultAttributes.width,
height: size, height: size ?? contextSize ?? defaultAttributes.height,
stroke: color, stroke: color ?? contextColor ?? defaultAttributes.stroke,
'stroke-width': 'stroke-width': calculatedStrokeWidth.value,
isEmptyString(absoluteStrokeWidth) ||
isEmptyString(absoluteStrokeWidthKebabCase) ||
absoluteStrokeWidth === true ||
absoluteStrokeWidthKebabCase === true
? (Number(strokeWidth || strokeWidthKebabCase || defaultAttributes['stroke-width']) *
24) /
Number(size)
: strokeWidth || strokeWidthKebabCase || defaultAttributes['stroke-width'],
class: mergeClasses( class: mergeClasses(
'lucide', 'lucide',
props.class, contextClass,
...(name ...(name
? [`lucide-${toKebabCase(toPascalCase(name))}-icon`, `lucide-${toKebabCase(name)}`] ? [`lucide-${toKebabCase(toPascalCase(name))}-icon`, `lucide-${toKebabCase(name)}`]
: ['lucide-icon']), : ['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 { h } from 'vue';
import type { FunctionalComponent } from 'vue'; import { IconNode, LucideIcon } from './types';
import { IconNode, LucideProps } from './types';
import Icon from './Icon'; import Icon from './Icon';
// Create interface extending SVGAttributes
/** /**
* Create a Lucide icon component * Create a Lucide icon component
* @param {string} iconName * @param {string} iconName
@@ -12,7 +9,7 @@ import Icon from './Icon';
* @returns {FunctionalComponent} LucideIcon * @returns {FunctionalComponent} LucideIcon
*/ */
const createLucideIcon = const createLucideIcon =
(iconName: string, iconNode: IconNode): FunctionalComponent<LucideProps> => (iconName: string, iconNode: IconNode): LucideIcon =>
(props, { slots, attrs }) => (props, { slots, attrs }) =>
h( h(
Icon, Icon,

View File

@@ -4,10 +4,8 @@ export interface LucideProps extends Partial<SVGAttributes> {
size?: 24 | number; size?: 24 | number;
strokeWidth?: number | string; strokeWidth?: number | string;
absoluteStrokeWidth?: boolean; absoluteStrokeWidth?: boolean;
'absolute-stroke-width'?: boolean;
} }
export type IconNode = [elementName: string, attrs: Record<string, string>][]; export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type LucideIcon = FunctionalComponent<LucideProps>; 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`] = ` exports[`Using lucide icon components > should add a class to the element 1`] = `
<div> <div>
<svg <svg
class="lucide my-icon lucide-smile-icon lucide-smile my-icon" class="lucide lucide-smile-icon lucide-smile my-icon"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" 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; const icon = container.firstElementChild;
await fireEvent.click(icon); await fireEvent.click(icon as Element);
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
}); });
@@ -153,7 +153,7 @@ describe('Using lucide icon components', () => {
props: { props: {
size: 48, size: 48,
color: 'red', 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, size: 48,
color: 'red', color: 'red',
'stroke-width': '2', '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"], "lib": ["ESNext", "DOM"],
"skipLibCheck": true, "skipLibCheck": true,
"noEmit": 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 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
preact: preact:
specifier: ^10.19.2 specifier: ^10.26.9
version: 10.27.2 version: 10.27.2
rollup: rollup:
specifier: ^4.53.3 specifier: ^4.53.3
@@ -448,7 +448,7 @@ importers:
specifier: ^18.2.37 specifier: ^18.2.37
version: 18.3.27 version: 18.3.27
'@vitejs/plugin-react': '@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)) 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: jest-serializer-html:
specifier: ^7.1.0 specifier: ^7.1.0
@@ -8827,6 +8827,9 @@ packages:
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
is-bigint@1.1.0: is-bigint@1.1.0:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8835,6 +8838,10 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'} engines: {node: '>=8'}
is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
is-boolean-object@1.2.2: is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8861,6 +8868,10 @@ packages:
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
engines: {node: '>= 0.4'} 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: is-date-object@1.1.0:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8918,6 +8929,9 @@ packages:
is-lambda@1.0.1: is-lambda@1.0.1:
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
is-map@2.0.2:
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
is-map@2.0.3: is-map@2.0.3:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8929,6 +8943,10 @@ packages:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-number-object@1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'}
is-number-object@1.1.1: is-number-object@1.1.1:
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -9014,6 +9032,9 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'} engines: {node: '>=10'}
is-weakmap@2.0.1:
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
is-weakmap@2.0.2: is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -9022,6 +9043,9 @@ packages:
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-weakset@2.0.2:
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
is-weakset@2.0.4: is-weakset@2.0.4:
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -11710,6 +11734,10 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'} 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: side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -12883,6 +12911,46 @@ packages:
terser: terser:
optional: true 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: vite@6.4.1:
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -13190,6 +13258,9 @@ packages:
whatwg-url@5.0.0: whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
which-boxed-primitive@1.1.1: which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -13198,6 +13269,9 @@ packages:
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
which-collection@1.0.1:
resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==}
which-collection@1.0.2: which-collection@1.0.2:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -15704,7 +15778,7 @@ snapshots:
'@babel/parser': 7.28.5 '@babel/parser': 7.28.5
'@babel/template': 7.27.2 '@babel/template': 7.27.2
'@babel/types': 7.28.5 '@babel/types': 7.28.5
debug: 4.4.3 debug: 4.3.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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))': '@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: 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)) '@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 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) 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: 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))': '@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: 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)) '@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 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) 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: transitivePeerDependencies:
@@ -18187,7 +18261,7 @@ snapshots:
'@testing-library/dom@8.20.1': '@testing-library/dom@8.20.1':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.27.1
'@babel/runtime': 7.27.1 '@babel/runtime': 7.26.0
'@types/aria-query': 5.0.4 '@types/aria-query': 5.0.4
aria-query: 5.1.3 aria-query: 5.1.3
chalk: 4.1.2 chalk: 4.1.2
@@ -18198,7 +18272,7 @@ snapshots:
'@testing-library/dom@9.3.4': '@testing-library/dom@9.3.4':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.27.1
'@babel/runtime': 7.27.1 '@babel/runtime': 7.26.0
'@types/aria-query': 5.0.4 '@types/aria-query': 5.0.4
aria-query: 5.1.3 aria-query: 5.1.3
chalk: 4.1.2 chalk: 4.1.2
@@ -18654,7 +18728,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) '@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) '@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 eslint: 8.57.1
ts-api-utils: 1.4.3(typescript@5.9.3) ts-api-utils: 1.4.3(typescript@5.9.3)
optionalDependencies: optionalDependencies:
@@ -18700,7 +18774,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/types': 6.21.0 '@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3 debug: 4.4.0
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.3 minimatch: 9.0.3
@@ -18863,13 +18937,13 @@ snapshots:
chai: 6.2.1 chai: 6.2.1
tinyrainbow: 3.0.3 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: dependencies:
'@vitest/spy': 4.0.12 '@vitest/spy': 4.0.12
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.21 magic-string: 0.30.21
optionalDependencies: 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': '@vitest/pretty-format@4.0.12':
dependencies: dependencies:
@@ -19948,7 +20022,7 @@ snapshots:
'@npmcli/move-file': 1.1.2 '@npmcli/move-file': 1.1.2
chownr: 2.0.0 chownr: 2.0.0
fs-minipass: 2.1.0 fs-minipass: 2.1.0
glob: 7.2.0 glob: 7.2.3
infer-owner: 1.0.4 infer-owner: 1.0.4
lru-cache: 6.0.0 lru-cache: 6.0.0
minipass: 3.3.6 minipass: 3.3.6
@@ -20620,7 +20694,7 @@ snapshots:
deep-equal@1.1.2: deep-equal@1.1.2:
dependencies: dependencies:
is-arguments: 1.1.1 is-arguments: 1.1.1
is-date-object: 1.1.0 is-date-object: 1.0.5
is-regex: 1.2.1 is-regex: 1.2.1
object-is: 1.1.6 object-is: 1.1.6
object-keys: 1.1.1 object-keys: 1.1.1
@@ -20634,7 +20708,7 @@ snapshots:
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
is-arguments: 1.1.1 is-arguments: 1.1.1
is-array-buffer: 3.0.5 is-array-buffer: 3.0.5
is-date-object: 1.1.0 is-date-object: 1.0.5
is-regex: 1.2.1 is-regex: 1.2.1
is-shared-array-buffer: 1.0.4 is-shared-array-buffer: 1.0.4
isarray: 2.0.5 isarray: 2.0.5
@@ -20642,9 +20716,9 @@ snapshots:
object-keys: 1.1.1 object-keys: 1.1.1
object.assign: 4.1.7 object.assign: 4.1.7
regexp.prototype.flags: 1.5.4 regexp.prototype.flags: 1.5.4
side-channel: 1.1.0 side-channel: 1.0.6
which-boxed-primitive: 1.1.1 which-boxed-primitive: 1.0.2
which-collection: 1.0.2 which-collection: 1.0.1
which-typed-array: 1.1.19 which-typed-array: 1.1.19
deep-is@0.1.4: {} deep-is@0.1.4: {}
@@ -21089,7 +21163,7 @@ snapshots:
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
has-symbols: 1.1.0 has-symbols: 1.1.0
is-arguments: 1.1.1 is-arguments: 1.1.1
is-map: 2.0.3 is-map: 2.0.2
is-set: 2.0.3 is-set: 2.0.3
is-string: 1.1.1 is-string: 1.1.1
isarray: 2.0.5 isarray: 2.0.5
@@ -22467,7 +22541,7 @@ snapshots:
dependencies: dependencies:
'@tootallnate/once': 1.1.2 '@tootallnate/once': 1.1.2
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.3 debug: 4.3.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -22699,6 +22773,10 @@ snapshots:
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
safe-regex-test: 1.1.0 safe-regex-test: 1.1.0
is-bigint@1.0.4:
dependencies:
has-bigints: 1.1.0
is-bigint@1.1.0: is-bigint@1.1.0:
dependencies: dependencies:
has-bigints: 1.1.0 has-bigints: 1.1.0
@@ -22707,6 +22785,11 @@ snapshots:
dependencies: dependencies:
binary-extensions: 2.3.0 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: is-boolean-object@1.2.2:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -22734,6 +22817,10 @@ snapshots:
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
is-typed-array: 1.1.15 is-typed-array: 1.1.15
is-date-object@1.0.5:
dependencies:
has-tostringtag: 1.0.2
is-date-object@1.1.0: is-date-object@1.1.0:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -22776,12 +22863,18 @@ snapshots:
is-lambda@1.0.1: {} is-lambda@1.0.1: {}
is-map@2.0.2: {}
is-map@2.0.3: {} is-map@2.0.3: {}
is-module@1.0.0: {} is-module@1.0.0: {}
is-negative-zero@2.0.3: {} is-negative-zero@2.0.3: {}
is-number-object@1.0.7:
dependencies:
has-tostringtag: 1.0.2
is-number-object@1.1.1: is-number-object@1.1.1:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -22851,12 +22944,19 @@ snapshots:
is-unicode-supported@0.1.0: {} is-unicode-supported@0.1.0: {}
is-weakmap@2.0.1: {}
is-weakmap@2.0.2: {} is-weakmap@2.0.2: {}
is-weakref@1.1.1: is-weakref@1.1.1:
dependencies: dependencies:
call-bound: 1.0.4 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: is-weakset@2.0.4:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -23858,7 +23958,7 @@ snapshots:
metro-runtime@0.81.0: metro-runtime@0.81.0:
dependencies: dependencies:
'@babel/runtime': 7.27.1 '@babel/runtime': 7.26.0
flow-enums-runtime: 0.0.6 flow-enums-runtime: 0.0.6
metro-runtime@0.81.5: metro-runtime@0.81.5:
@@ -25785,7 +25885,7 @@ snapshots:
qs@6.13.0: qs@6.13.0:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.0.6
quansync@0.2.11: {} quansync@0.2.11: {}
@@ -26696,6 +26796,13 @@ snapshots:
object-inspect: 1.13.4 object-inspect: 1.13.4
side-channel-map: 1.0.1 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: side-channel@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -26787,7 +26894,7 @@ snapshots:
socks-proxy-agent@6.2.1: socks-proxy-agent@6.2.1:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.3 debug: 4.3.3
socks: 2.8.3 socks: 2.8.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -26795,7 +26902,7 @@ snapshots:
socks-proxy-agent@7.0.0: socks-proxy-agent@7.0.0:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.3 debug: 4.3.3
socks: 2.8.3 socks: 2.8.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -27095,7 +27202,7 @@ snapshots:
dependencies: dependencies:
css: 3.0.0 css: 3.0.0
debug: 4.4.3 debug: 4.4.3
glob: 7.2.0 glob: 7.2.3
safer-buffer: 2.1.2 safer-buffer: 2.1.2
sax: 1.2.4 sax: 1.2.4
source-map: 0.7.4 source-map: 0.7.4
@@ -28010,6 +28117,24 @@ snapshots:
stylus: 0.56.0 stylus: 0.56.0
terser: 5.44.1 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): 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: dependencies:
esbuild: 0.25.10 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): 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: dependencies:
'@vitest/expect': 4.0.12 '@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/pretty-format': 4.0.12
'@vitest/runner': 4.0.12 '@vitest/runner': 4.0.12
'@vitest/snapshot': 4.0.12 '@vitest/snapshot': 4.0.12
@@ -28127,7 +28252,7 @@ snapshots:
tinyexec: 0.3.2 tinyexec: 0.3.2
tinyglobby: 0.2.15 tinyglobby: 0.2.15
tinyrainbow: 3.0.3 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 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/debug': 4.1.12 '@types/debug': 4.1.12
@@ -28329,6 +28454,14 @@ snapshots:
tr46: 0.0.3 tr46: 0.0.3
webidl-conversions: 3.0.1 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: which-boxed-primitive@1.1.1:
dependencies: dependencies:
is-bigint: 1.1.0 is-bigint: 1.1.0
@@ -28353,6 +28486,13 @@ snapshots:
which-collection: 1.0.2 which-collection: 1.0.2
which-typed-array: 1.1.19 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: which-collection@1.0.2:
dependencies: dependencies:
is-map: 2.0.3 is-map: 2.0.3