From 954dcca44b751ac9f9db22fbe3e5053fe0c43482 Mon Sep 17 00:00:00 2001 From: Hollujay Date: Thu, 23 Jul 2026 15:16:50 +0100 Subject: [PATCH 1/2] feat: add chart series toggling via legend (#214) --- src/components/Chart.css | 42 ++++++++ src/components/Chart.jsx | 175 ++++++++++++++++++++++++++----- src/components/Chart.stories.jsx | 32 ++++++ test/components/Chart.test.jsx | 75 +++++++++++++ 4 files changed, 296 insertions(+), 28 deletions(-) diff --git a/src/components/Chart.css b/src/components/Chart.css index 78320e2..44f506e 100644 --- a/src/components/Chart.css +++ b/src/components/Chart.css @@ -65,6 +65,48 @@ } } +.chart-legend { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.75rem; + justify-content: center; +} + +.chart-legend-item { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.25rem 0.5rem; + border: 1px solid transparent; + border-radius: 4px; + background: none; + cursor: pointer; + font-size: 0.75rem; + transition: opacity 0.15s ease; +} + +.chart-legend-item:hover { + border-color: #ddd; +} + +.chart-legend-item--dimmed { + opacity: 0.4; + text-decoration: line-through; +} + +.chart-legend-swatch { + display: inline-block; + width: 0.75rem; + height: 0.75rem; + border-radius: 2px; + flex-shrink: 0; +} + +.chart-legend-label { + white-space: nowrap; +} + .download-button { margin-top: 1rem; } diff --git a/src/components/Chart.jsx b/src/components/Chart.jsx index 9f4195a..3336c1a 100644 --- a/src/components/Chart.jsx +++ b/src/components/Chart.jsx @@ -1,10 +1,53 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useMemo } from 'react'; import './Chart.css'; import Button from './Button.jsx'; -export default function Chart({ data, title, formatValue }) { +const DEFAULT_COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6']; + +export default function Chart({ data, series, title, formatValue }) { const chartRef = useRef(null); - const [hoveredIndex, setHoveredIndex] = useState(null); + const [hoveredBar, setHoveredBar] = useState(null); + const [hiddenSeries, setHiddenSeries] = useState(new Set()); + + const isMultiSeries = Array.isArray(series) && series.length > 0; + + const normalized = useMemo(() => { + if (isMultiSeries) { + const allSeries = series.map((s, i) => ({ + name: s.name, + color: s.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length], + data: s.data, + })); + const barCount = Math.max(...allSeries.map(s => s.data.length), 0); + const visibleSeries = allSeries.filter(s => !hiddenSeries.has(s.name)); + const maxValue = Math.max(...visibleSeries.flatMap(s => s.data.map(d => d.value)), 1); + return { allSeries, barCount, maxValue }; + } + return { + allSeries: [{ name: null, color: '#6366f1', data }], + barCount: data.length, + maxValue: Math.max(...data.map(d => d.value), 1), + }; + }, [data, series, hiddenSeries, isMultiSeries]); + + const { allSeries, barCount, maxValue } = normalized; + const numSeries = allSeries.length; + const groupWidth = barCount > 0 ? 100 / barCount : 100; + const barWidth = isMultiSeries && barCount > 0 + ? (groupWidth - 2) / numSeries + : groupWidth - 2; + + const getBarHeight = (v) => (v / maxValue) * 100; + const getBarY = (v) => 100 - getBarHeight(v); + + const toggleSeries = (name) => { + setHiddenSeries(prev => { + const next = new Set(prev); + if (next.has(name)) next.delete(name); + else next.add(name); + return next; + }); + }; const downloadChart = () => { const svg = chartRef.current; @@ -21,39 +64,115 @@ export default function Chart({ data, title, formatValue }) { URL.revokeObjectURL(url); }; - const maxValue = Math.max(...data.map(d => d.value), 1); - const barCount = data.length; + const renderBars = () => { + if (isMultiSeries) { + return Array.from({ length: barCount }).map((_, i) => + allSeries.map((s, si) => { + const dp = s.data[i]; + if (!dp) return null; + const isHidden = hiddenSeries.has(s.name); + return ( + setHoveredBar({ seriesIndex: si, itemIndex: i })} + onMouseLeave={() => setHoveredBar(null)} + /> + ); + }) + ); + } + + if (!data) return null; + return data.map((d, i) => { + const isHighlighted = hoveredBar?.itemIndex === i; + return ( + setHoveredBar({ seriesIndex: 0, itemIndex: i })} + onMouseLeave={() => setHoveredBar(null)} + /> + ); + }); + }; + + const renderTooltip = () => { + if (!hoveredBar) return null; + const { seriesIndex, itemIndex } = hoveredBar; + + if (isMultiSeries) { + const s = allSeries[seriesIndex]; + const dp = s?.data[itemIndex]; + if (!dp) return null; + return ( +
+
+ {s.name}: {formatValue ? formatValue(dp, s.name) : dp.value} +
+ {dp.label &&
{dp.label}
} +
+ ); + } + + const dp = data[itemIndex]; + return ( +
+
+ {formatValue ? formatValue(dp) : dp.value} +
+ {dp.label &&
{dp.label}
} +
+ ); + }; return (

{title}

- {data.map((d, i) => ( - setHoveredIndex(i)} - onMouseLeave={() => setHoveredIndex(null)} - /> - ))} + {renderBars()} - {hoveredIndex !== null && ( -
-
- {formatValue ? formatValue(data[hoveredIndex]) : data[hoveredIndex].value} -
- {data[hoveredIndex].label && ( -
{data[hoveredIndex].label}
- )} -
- )} + {renderTooltip()}
+ {isMultiSeries && ( +
+ {series.map((s) => { + const isHidden = hiddenSeries.has(s.name); + return ( + + ); + })} +
+ )}
diff --git a/src/components/Chart.stories.jsx b/src/components/Chart.stories.jsx index ce994d3..4ffc4bc 100644 --- a/src/components/Chart.stories.jsx +++ b/src/components/Chart.stories.jsx @@ -8,6 +8,7 @@ export default { argTypes: { title: { control: 'text' }, data: { control: 'object' }, + series: { control: 'object' }, formatValue: { control: false } } } @@ -48,3 +49,34 @@ export const SingleBar = { formatValue: (d) => formatAmount(d.value, d.currency) } } + +export const MultiSeries = { + args: { + title: 'Sent vs Received Amounts', + series: [ + { + name: 'Sent', + color: '#6366f1', + data: [ + { value: 200, label: 'amina@example.com' }, + { value: 120, label: 'GBQAZ7Z3X7...' }, + { value: 450, label: 'chidi@example.com' }, + { value: 80, label: 'devi@example.com' }, + { value: 310, label: 'emeka@example.com' } + ] + }, + { + name: 'Received', + color: '#10b981', + data: [ + { value: 180, label: 'amina@example.com' }, + { value: 100, label: 'GBQAZ7Z3X7...' }, + { value: 500, label: 'chidi@example.com' }, + { value: 60, label: 'devi@example.com' }, + { value: 280, label: 'emeka@example.com' } + ] + } + ], + formatValue: (d) => `$${d.value.toFixed(2)}` + } +} diff --git a/test/components/Chart.test.jsx b/test/components/Chart.test.jsx index 7b278e4..3c732a2 100644 --- a/test/components/Chart.test.jsx +++ b/test/components/Chart.test.jsx @@ -67,4 +67,79 @@ describe('Chart component', () => { expect(tooltip).toHaveTextContent('100') expect(tooltip).toHaveTextContent('alice@example.com') }) + + describe('multi-series with legend', () => { + const seriesData = [ + { name: 'Sent', color: '#6366f1', data: [{ value: 200 }, { value: 120 }] }, + { name: 'Received', color: '#10b981', data: [{ value: 180 }, { value: 100 }] } + ] + + it('renders legend when series prop is provided', () => { + render() + expect(screen.getByText('Sent')).toBeInTheDocument() + expect(screen.getByText('Received')).toBeInTheDocument() + }) + + it('renders correct number of bars for multi-series', () => { + const { container } = render() + const bars = container.querySelectorAll('.chart-bar') + expect(bars).toHaveLength(4) + }) + + it('toggles series visibility on legend click', async () => { + const user = userEvent.setup() + const { container } = render() + + expect(container.querySelectorAll('.chart-bar')).toHaveLength(4) + + await user.click(screen.getByText('Sent')) + + const bars = container.querySelectorAll('.chart-bar') + expect(bars).toHaveLength(4) + expect(bars[0].getAttribute('opacity')).toBe('0.3') + expect(bars[1].getAttribute('opacity')).toBe('1') + expect(bars[2].getAttribute('opacity')).toBe('0.3') + expect(bars[3].getAttribute('opacity')).toBe('1') + }) + + it('dims legend item when series is hidden', async () => { + const user = userEvent.setup() + render() + + const sentButton = screen.getByText('Sent') + expect(sentButton.closest('.chart-legend-item--dimmed')).toBeNull() + + await user.click(sentButton) + expect(sentButton.closest('.chart-legend-item--dimmed')).toBeInTheDocument() + + await user.click(sentButton) + expect(sentButton.closest('.chart-legend-item--dimmed')).toBeNull() + }) + + it('shows series name in tooltip on bar hover', async () => { + const user = userEvent.setup() + const { container } = render() + const bars = container.querySelectorAll('.chart-bar') + + await user.hover(bars[0]) + expect(screen.getByRole('tooltip')).toHaveTextContent('Sent') + }) + + it('does not render legend when using data prop', () => { + const { container } = render() + expect(container.querySelector('.chart-legend')).toBeNull() + }) + + it('uses default colors when series omit color', () => { + const seriesNoColor = [ + { name: 'A', data: [{ value: 10 }] }, + { name: 'B', data: [{ value: 20 }] } + ] + const { container } = render() + const bars = container.querySelectorAll('.chart-bar') + expect(bars).toHaveLength(2) + expect(bars[0]).toHaveAttribute('fill', '#6366f1') + expect(bars[1]).toHaveAttribute('fill', '#10b981') + }) + }) }) From ccb982dca205e08fe567dcee7eb37d2331ddcac5 Mon Sep 17 00:00:00 2001 From: Hollujay Date: Thu, 23 Jul 2026 15:17:11 +0100 Subject: [PATCH 2/2] chore: update package-lock.json --- package-lock.json | 512 ---------------------------------------------- 1 file changed, 512 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac54508..e7969f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10073,474 +10073,6 @@ } } }, - "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/vitest/node_modules/@vitest/mocker": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", @@ -10568,50 +10100,6 @@ } } }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, "node_modules/vitest/node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",