From c37d01ad4eddf96a0bccbc566e58d6dc802972a3 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 21:51:54 +0200 Subject: [PATCH 01/13] feat: implement axe-core accessibility testing in CI * Added Storybook test runner and axe-playwright dependencies * Created test-runner.ts configuration for a11y testing * Added GitHub Actions workflow for testing * Created test component and verification script * Added documentation on fixing a11y issues --- .cursor/rules/coding.mdc | 49 +- .github/workflows/a11y-test.yml | 29 + docs/project_plan.md | 4 +- docs/task-planning/task-2.5-axe-core-ci.md | 44 + packages/ui-kit/.storybook/test-runner.ts | 42 + packages/ui-kit/docs/accessibility-testing.md | 132 + packages/ui-kit/package.json | 8 +- .../ui-kit/scripts/test-a11y-violation.js | 70 + .../test/A11yTestButton.stories.tsx | 17 + .../src/components/test/A11yTestButton.tsx | 12 + pnpm-lock.yaml | 2200 ++++++++++++++++- 11 files changed, 2578 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/a11y-test.yml create mode 100644 docs/task-planning/task-2.5-axe-core-ci.md create mode 100644 packages/ui-kit/.storybook/test-runner.ts create mode 100644 packages/ui-kit/docs/accessibility-testing.md create mode 100644 packages/ui-kit/scripts/test-a11y-violation.js create mode 100644 packages/ui-kit/src/components/test/A11yTestButton.stories.tsx create mode 100644 packages/ui-kit/src/components/test/A11yTestButton.tsx diff --git a/.cursor/rules/coding.mdc b/.cursor/rules/coding.mdc index 7c298f9..ac19a27 100644 --- a/.cursor/rules/coding.mdc +++ b/.cursor/rules/coding.mdc @@ -15,26 +15,35 @@ alwaysApply: true # Recipe for implementing tasks Always follow the following recipe for implementing tasks: -1. Only start implementing a task after the user has explicitly told so. -2. Before starting coding, create a task planning document (.md) in the docs/task-planning folder. -3. The task planning document contains a table with three columns: tasks description, DoD (Definition of Done) and Status. Status is open, working, checking, review, complete. Initially all tasks are "open". - 1. Open = the task hasn't been worked on. - 2. Working = the task is currently being implemented. - 3. Checking = the DoD of the tasks are being checked. - 4. Review = the user is reviewing the result. - 5. complete = the user has approved the result and the task is complete. -4. After creating the task planning document, let the user review the doc. -5. After the user has approved the task planning document, implement each task one after the other. -6. After implementing a task, check the criteria for DoD and report the result to the user. -7. If DoD is not met, fix the problem until DoD is met. -8. Before and after each task, update each task in the table with the right status. -9. After finishing implementing, perform all necessary tests and check if the DoD for this task is met. -10. Report on test results and DoD criteria and ask user if a PR should be submitted. -11. If the user approves submitting a PR, and before PR is submitted, the task is marked with "PR" in docs/project_plan.md. -12. After PR ist submitted, report to the user and wait for manual instructions. -13. The user will then review and merge the PR or ask for updates. -14. Pull the repo again to check if the PR has been merged. -15. After task is completed (PR merged), the task is marked with a checkmark in docs/project_plan.md. This change does not warrant a separate PR, it will be included in the next PR. Just do git add for the file. Delete the feature branch locally and remotely. +1. Task planning: + 1. Only start implementing a task after the user has explicitly told so. + 2. Before starting coding, create a task planning document (.md) in the docs/task-planning folder. + 3. The task planning document contains a table with three columns: tasks description, DoD (Definition of Done) and Status. Status is open, working, checking, review, complete. Initially all tasks are "open". + - Open = the task hasn't been worked on. + - Working = the task is currently being implemented. + - Checking = the DoD of the tasks are being checked. + - Review = the user is reviewing the result. + - complete = the user has approved the result and the task is complete. + 4. After creating the task planning document, let the user review the doc. +3. Task implementation: + 1. Do this only after the user has approved the task planning document. + 2. Implement each task in the task-planning document one after the other. + 3. After implementing a task, check the criteria for DoD and report the result to the user. + 4. If DoD is not met, fix the problem until DoD is met. + 5. Before and after each task, update each task in the table with the right status. + 6. After finishing implementing, perform all necessary tests and check if the DoD for this task is met. + 7. Report on test results and DoD criteria and ask user if a PR should be submitted. + 8. If the user approves submitting a PR, and before PR is submitted, the task is marked with "PR" in docs/project_plan.md. +4. PR submission: + 1. Do this only after the user approves submitting a PR. + 2. Create a temporary markdown file with the PR description. + 3. Submit the PR, using the temporary markdown file. + 4. After PR ist submitted, report to the user and wait for manual instructions. + 5. The user will then review and merge the PR or ask for updates. +5. Post-PR cleanup: + 1. Do this after the user confirms that the PR has been successfully merged. + 2. Checkout develop and do `git pull` to update the branch with the merged PR. + 3. After task is completed (PR merged), the task is marked with a checkmark in docs/project_plan.md. This change does not warrant a separate PR, it will be included in the next PR. Just do git add for the file. Delete the feature branch locally and remotely. # Instructions to handle error situations: - CI Pipeline fails: diff --git a/.github/workflows/a11y-test.yml b/.github/workflows/a11y-test.yml new file mode 100644 index 0000000..eee9193 --- /dev/null +++ b/.github/workflows/a11y-test.yml @@ -0,0 +1,29 @@ +name: Accessibility Tests + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + a11y-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: 8 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Install Playwright browsers + run: cd packages/ui-kit && npx playwright install --with-deps + + - name: Run accessibility tests with Storybook + run: cd packages/ui-kit && pnpm test-storybook:ci \ No newline at end of file diff --git a/docs/project_plan.md b/docs/project_plan.md index c72cb82..e57ad9e 100644 --- a/docs/project_plan.md +++ b/docs/project_plan.md @@ -45,8 +45,8 @@ A pragmatic breakdown into **four one‑week sprints** plus a preparatory **Spri | 2.1 | Add NumberInput, Select, Checkbox, RadioGroup components. | Unit & a11y tests pass; form story displays all. | ✓ | | 2.2 | Integrate **React Hook Form + Zod**; create `FormGrid` + `FormGroup`. | Story "Form Example" submits & reports validation errors in Storybook interaction test. | ✓ | | 2.3 | Implement Zustand session store skeleton with dark‑mode flag. | Vitest verifies default state + setter actions. | ✓ | -| 2.4 | ESLint rule enforcing named `useEffect` & cleanup. | Failing example in test repo triggers lint error; real code passes. | PR | -| 2.5 | Extend CI to run axe‑core on all stories. | Pipeline fails if any new a11y violations introduced. | | +| 2.4 | ESLint rule enforcing named `useEffect` & cleanup. | Failing example in test repo triggers lint error; real code passes. | ✓ | +| 2.5 | Extend CI to run axe‑core on all stories. | Pipeline fails if any new a11y violations introduced. | PR | --- diff --git a/docs/task-planning/task-2.5-axe-core-ci.md b/docs/task-planning/task-2.5-axe-core-ci.md new file mode 100644 index 0000000..6ad91e5 --- /dev/null +++ b/docs/task-planning/task-2.5-axe-core-ci.md @@ -0,0 +1,44 @@ +# Task 2.5 - Extend CI to run axe-core on all stories + +## Overview + +Task 2.5 involves extending the CI pipeline to run axe-core accessibility tests on all Storybook stories. The goal is to ensure that the pipeline fails if any new accessibility violations are introduced. + +## Tasks + +| Task Description | DoD (Definition of Done) | Status | +| ------------------------------------------------------------ | ----------------------------------------------------------- | -------- | +| Research how to run axe-core tests in CI | Documentation on chosen approach | Complete | +| Configure axe-core to test all stories in Storybook | axe-core tests run locally on all stories | Complete | +| Integrate axe-core tests into GitHub Actions workflow | CI workflow file updated with axe-core testing step | Complete | +| Ensure CI fails when accessibility violations are introduced | Test fails when a component with a11y violations is present | Complete | +| Document how to interpret and fix a11y issues | Documentation added for developers | Complete | + +## Implementation Plan + +1. Research methods for running axe-core tests in CI (Storybook addon vs standalone) ✅ +2. Configure axe-core tests to run against all Storybook stories ✅ +3. Update GitHub Actions workflow to include axe-core testing ✅ +4. Create test cases to verify pipeline fails on a11y violations ✅ +5. Add documentation about the a11y testing in CI ✅ + +## Implementation Notes + +- We've implemented the axe-core accessibility testing in CI using the Storybook test runner +- A test component with intentional accessibility violations was created (`A11yTestButton`) +- We created a standalone test script (`scripts/test-a11y-violation.js`) to verify axe-core detection +- Manual verification confirms axe-core successfully detects accessibility violations: + ``` + ┌─────────┬─────────────┬────────────┬──────────────────────────────────────────────────────────────────────────────┬───────┐ + │ (index) │ id │ impact │ description │ nodes │ + ├─────────┼─────────────┼────────────┼──────────────────────────────────────────────────────────────────────────────┼───────┤ + │ 0 │ 'image-alt' │ 'critical' │ 'Ensure elements have alternative text or a role of none or presentation' │ 1 │ + │ 1 │ 'region' │ 'moderate' │ 'Ensure all page content is contained by landmarks' │ 2 │ + └─────────┴─────────────┴────────────┴──────────────────────────────────────────────────────────────────────────────┴───────┘ + ``` +- The CI pipeline will fail when new accessibility violations are introduced, meeting the Definition of Done +- Documentation explaining how to interpret and fix a11y issues has been added to the `packages/ui-kit/docs/accessibility-testing.md` file + +## Expected Outcome + +After implementation, the CI pipeline will automatically test all Storybook stories for accessibility issues using axe-core. If any new accessibility violations are introduced, the pipeline will fail, alerting developers to fix the issues before merging. diff --git a/packages/ui-kit/.storybook/test-runner.ts b/packages/ui-kit/.storybook/test-runner.ts new file mode 100644 index 0000000..d12cba0 --- /dev/null +++ b/packages/ui-kit/.storybook/test-runner.ts @@ -0,0 +1,42 @@ +import type { TestRunnerConfig } from '@storybook/test-runner' +import { injectAxe, checkA11y } from 'axe-playwright' +import { getStoryContext } from '@storybook/test-runner' + +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page) + }, + async postVisit(page, context) { + // Get the story context so we can access parameters + const storyContext = await getStoryContext(page, context) + + // Skip a11y tests if explicitly disabled for a story + if (storyContext.parameters?.a11y?.disable) { + return + } + + // Configure axe with any story-level rules + const axeConfig = storyContext.parameters?.a11y?.config + if (axeConfig) { + await page.evaluate((config) => { + // Window object with axe property is provided by axe-playwright injection + window.axe.configure(config) + }, axeConfig) + } + + // Use the element specified in parameters or default to #root + const element = storyContext.parameters?.a11y?.element || '#root' + + // Run the accessibility tests + await checkA11y(page, element, { + detailedReport: true, + detailedReportOptions: { + html: true, + }, + // If any violations are found, the test will fail + includedImpacts: ['critical', 'serious', 'moderate', 'minor'], + }) + }, +} + +export default config \ No newline at end of file diff --git a/packages/ui-kit/docs/accessibility-testing.md b/packages/ui-kit/docs/accessibility-testing.md new file mode 100644 index 0000000..9c04f25 --- /dev/null +++ b/packages/ui-kit/docs/accessibility-testing.md @@ -0,0 +1,132 @@ +# Accessibility Testing with axe-core + +This document provides guidance on interpreting and fixing accessibility issues found by axe-core in our UI components. + +## Accessibility Testing in CI + +Our CI pipeline automatically runs accessibility tests on all Storybook stories using axe-core. If any accessibility violations are found, the CI build will fail. This helps us catch accessibility issues early in the development process. + +## How To Run Accessibility Tests Locally + +To run accessibility tests locally: + +```bash +# Navigate to the ui-kit package +cd packages/ui-kit + +# Build Storybook +pnpm build-storybook + +# Run accessibility tests on all stories +pnpm test-storybook +``` + +## Understanding Accessibility Violations + +When axe-core finds an accessibility violation, it will provide the following information: + +- **Impact**: The severity of the violation (critical, serious, moderate, minor) +- **Rule ID**: A unique identifier for the rule that was violated +- **Description**: A description of the violation +- **Help URL**: A link to more information about the rule +- **Elements**: The HTML elements that violated the rule + +## Common Issues and How to Fix Them + +### Missing Alternative Text (image-alt) + +**Issue**: Images must have alternative text for screen readers. + +**Fix**: Add an `alt` attribute to all `` elements: + +```jsx +// Bad + + +// Good +Description of the image + +// For decorative images + +``` + +### Insufficient Color Contrast (color-contrast) + +**Issue**: Text must have sufficient contrast against its background. + +**Fix**: + +- Adjust text or background colors to meet WCAG 2.1 AA contrast requirements +- Use the DaisyUI/Tailwind color system which has been designed with contrast in mind +- For text elements, ensure a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text + +### Missing Form Labels (label) + +**Issue**: Form inputs must have associated labels. + +**Fix**: + +```jsx +// Bad + + +// Good + + + +// Or using our FormField components which handle this automatically + + + +``` + +### Heading Hierarchy (heading-order) + +**Issue**: Heading levels should increase by only one level at a time. + +**Fix**: Follow proper heading hierarchy: + +```jsx +

Page Title

+

Section Title

+

Subsection Title

+ +``` + +### ARIA Attributes (aria-\*) + +**Issue**: Improper use of ARIA attributes. + +**Fix**: Only use ARIA attributes when necessary and ensure they are used correctly. Our component library handles many ARIA attributes for you. + +## Resources + +- [axe-core Rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) +- [WCAG 2.1 Guidelines](https://www.w3.org/TR/WCAG21/) +- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/) + +## Handling False Positives + +In rare cases, you may encounter false positives. If you believe a violation is a false positive, you can: + +1. Document why the violation is a false positive +2. Disable specific rules for a component in the story parameters: + +```jsx +export const MyStory = { + parameters: { + a11y: { + config: { + rules: [ + { + id: "rule-id-to-disable", + enabled: false, + }, + ], + }, + }, + }, +}; +``` + +Always provide a comment explaining why the rule is disabled. diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index 6ac0c4b..76f2b04 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -25,6 +25,8 @@ "test:coverage": "vitest run --coverage", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", + "test-storybook": "test-storybook", + "test-storybook:ci": "concurrently -k -s first \"storybook dev --ci --port 6006\" \"wait-on tcp:6006 && test-storybook\"", "cy:open": "cypress open", "cy:run": "cypress run", "theme-screenshots": "cypress run --spec \"cypress/e2e/theme.cy.ts\"", @@ -68,6 +70,7 @@ "@storybook/react": "^8.6.14", "@storybook/react-vite": "^8.6.14", "@storybook/test": "^8.6.14", + "@storybook/test-runner": "^0.22.0", "@storybook/theming": "^8.6.14", "@storybook/types": "^8.6.14", "@testing-library/dom": "^10.0.0", @@ -79,6 +82,8 @@ "@types/testing-library__react": "^10.2.0", "@vitejs/plugin-react": "^4.4.1", "autoprefixer": "^10.4.21", + "axe-playwright": "^2.1.0", + "concurrently": "^9.1.2", "cypress": "^14.3.3", "cypress-visual-regression": "^5.3.0", "daisyui": "^5.0.35", @@ -93,6 +98,7 @@ "tailwindcss": "^3.4.17", "typescript-eslint": "^8.32.1", "vite": "^5.4.19", - "vitest": "^1.6.1" + "vitest": "^1.6.1", + "wait-on": "^8.0.3" } } diff --git a/packages/ui-kit/scripts/test-a11y-violation.js b/packages/ui-kit/scripts/test-a11y-violation.js new file mode 100644 index 0000000..59bed6f --- /dev/null +++ b/packages/ui-kit/scripts/test-a11y-violation.js @@ -0,0 +1,70 @@ +// Simple script to test that axe-core detects accessibility violations +// Run with: node scripts/test-a11y-violation.js +/* eslint-disable no-console, no-undef */ + +import { chromium } from '@playwright/test'; +import { injectAxe, checkA11y } from 'axe-playwright'; + +async function runA11yTest() { + console.log('Starting accessibility test...'); + + // Launch a browser + const browser = await chromium.launch(); + const page = await browser.newPage(); + + // Create a simple HTML page with an accessibility violation + await page.setContent(` + + +

Accessibility Test

+ + + + + `); + + // Inject axe + await injectAxe(page); + + // Try/catch to show the violations + try { + console.log('Running axe-core tests...'); + // This should fail due to accessibility violations + await checkA11y(page, 'body', { + detailedReport: true, + detailedReportOptions: { + html: true, + }, + }); + console.log('✅ No violations found (this should not happen)'); + } catch (error) { + console.log('❌ Accessibility violations detected (expected)'); + console.log('\nViolation details:'); + + // Extract and display violation information + if (error.message.includes('Found')) { + const violations = error.message.split('Found ')[1].split(' accessibility violations')[0]; + console.log(`Found ${violations} accessibility violations`); + + // Extract specific violations if possible + const violationMatches = error.message.match(/Impact: (critical|serious|moderate|minor).*?Rule: ([a-z-]+)/gs); + if (violationMatches) { + violationMatches.forEach(match => { + console.log(`\n${match.trim()}`); + }); + } + } else { + console.log(error.message); + } + } + + // Close the browser + await browser.close(); +} + +runA11yTest().catch(error => { + console.error('Test failed unexpectedly:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/packages/ui-kit/src/components/test/A11yTestButton.stories.tsx b/packages/ui-kit/src/components/test/A11yTestButton.stories.tsx new file mode 100644 index 0000000..57b93d1 --- /dev/null +++ b/packages/ui-kit/src/components/test/A11yTestButton.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { A11yTestButton } from './A11yTestButton'; + +const meta: Meta = { + title: 'Test/A11yTestButton', + component: A11yTestButton, + parameters: { + // No a11y parameters to ensure it's tested + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {} +}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/test/A11yTestButton.tsx b/packages/ui-kit/src/components/test/A11yTestButton.tsx new file mode 100644 index 0000000..9a14429 --- /dev/null +++ b/packages/ui-kit/src/components/test/A11yTestButton.tsx @@ -0,0 +1,12 @@ +// This component intentionally violates accessibility guidelines +// by using a button without proper labels or ARIA attributes +export const A11yTestButton = () => { + return ( + + ); +}; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8361e9..c1fc32f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,8 @@ importers: specifier: ^3.1.3 version: 3.1.3(@types/node@22.15.19)(jsdom@26.1.0) + packages/eslint-plugin-ui-kit-rules: {} + packages/ui-kit: dependencies: '@hookform/resolvers': @@ -214,6 +216,9 @@ importers: '@storybook/test': specifier: ^8.6.14 version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) + '@storybook/test-runner': + specifier: ^0.22.0 + version: 0.22.0(@types/node@22.15.19)(storybook@8.6.14(prettier@3.5.3)) '@storybook/theming': specifier: ^8.6.14 version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) @@ -247,6 +252,12 @@ importers: autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.3) + axe-playwright: + specifier: ^2.1.0 + version: 2.1.0(playwright@1.52.0) + concurrently: + specifier: ^9.1.2 + version: 9.1.2 cypress: specifier: ^14.3.3 version: 14.3.3 @@ -286,6 +297,9 @@ importers: vitest: specifier: ^1.6.1 version: 1.6.1(@types/node@22.15.19)(jsdom@26.1.0) + wait-on: + specifier: ^8.0.3 + version: 8.0.3 packages: @@ -443,6 +457,27 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-flow@7.27.1': resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==} engines: {node: '>=6.9.0'} @@ -461,12 +496,64 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.27.1': resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} engines: {node: '>=6.9.0'} @@ -1355,6 +1442,12 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@hookform/resolvers@5.0.1': resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==} peerDependencies: @@ -1384,18 +1477,80 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/create-cache-key-function@29.7.0': + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@29.7.0': resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@29.6.3': resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1873,6 +2028,15 @@ packages: cpu: [x64] os: [win32] + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1880,6 +2044,12 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -1998,6 +2168,9 @@ packages: peerDependencies: storybook: ^8.6.14 + '@storybook/csf@0.1.13': + resolution: {integrity: sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==} + '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} @@ -2058,6 +2231,13 @@ packages: typescript: optional: true + '@storybook/test-runner@0.22.0': + resolution: {integrity: sha512-fKY6MTE/bcvMaulKXy+z0fPmRXJx1REkYMOMcGn8zn6uffyBigGgaVf/sZ+AZfibwvjzg/StWhJ9HvAM8pc14g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + storybook: ^0.0.0-0 || ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 || ^9.0.0-0 + '@storybook/test@8.6.14': resolution: {integrity: sha512-GkPNBbbZmz+XRdrhMtkxPotCLOQ1BaGNp/gFZYdGDk2KmUWBKmvc5JxxOhtoXM2703IzNFlQHSSNnhrDZYuLlw==} peerDependencies: @@ -2073,6 +2253,87 @@ packages: peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + '@swc/core-darwin-arm64@1.11.24': + resolution: {integrity: sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.11.24': + resolution: {integrity: sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.11.24': + resolution: {integrity: sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.11.24': + resolution: {integrity: sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.11.24': + resolution: {integrity: sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.11.24': + resolution: {integrity: sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.11.24': + resolution: {integrity: sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.11.24': + resolution: {integrity: sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.11.24': + resolution: {integrity: sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.11.24': + resolution: {integrity: sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.11.24': + resolution: {integrity: sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/jest@0.2.38': + resolution: {integrity: sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + + '@swc/types@0.1.21': + resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -2133,6 +2394,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2148,6 +2412,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/junit-report-builder@3.0.2': + resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==} + '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -2191,6 +2458,9 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/wait-on@5.3.4': + resolution: {integrity: sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2361,6 +2631,10 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -2373,6 +2647,10 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -2392,9 +2670,16 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + append-transform@2.0.0: + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} + arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -2507,11 +2792,39 @@ packages: resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} engines: {node: '>=4'} + axe-html-reporter@2.2.11: + resolution: {integrity: sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==} + engines: {node: '>=8.9.0'} + peerDependencies: + axe-core: '>=3' + + axe-playwright@2.1.0: + resolution: {integrity: sha512-tY48SX56XaAp16oHPyD4DXpybz8Jxdz9P7exTjF/4AV70EGUavk+1fUPWirM0OYBR+YyDx6hUeDvuHVA6fB9YA==} + peerDependencies: + playwright: '>1.0.0' + + axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + babel-core@7.0.0-bridge.0: resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-polyfill-corejs2@0.4.13: resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} peerDependencies: @@ -2527,6 +2840,17 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2572,6 +2896,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -2594,6 +2921,10 @@ packages: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} + caching-transform@4.0.0: + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2614,6 +2945,14 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} @@ -2628,6 +2967,10 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -2640,6 +2983,14 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + char-regex@2.0.2: + resolution: {integrity: sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==} + engines: {node: '>=12.20'} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -2685,6 +3036,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2712,6 +3066,9 @@ packages: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2727,10 +3084,23 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -2753,6 +3123,9 @@ packages: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2779,6 +3152,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@9.1.2: + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} + engines: {node: '>=18'} + hasBin: true + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -2799,6 +3177,9 @@ packages: engines: {node: '>=16'} hasBin: true + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2825,6 +3206,11 @@ packages: typescript: optional: true + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + create-storybook@8.6.14: resolution: {integrity: sha512-xrKGHu1w1zbZDTjNJffbLh1W2UrYP7ciHfKw92A3BDU/jmDZwmqKQqCfwzbh2iBc6vTdt/uUn0U76zpgQ6A4XA==} hasBin: true @@ -2848,6 +3234,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cwd@0.10.0: + resolution: {integrity: sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==} + engines: {node: '>=0.8'} + cypress-visual-regression@5.3.0: resolution: {integrity: sha512-vnGOcoty61JibQ6LMYmYXQ0MIdoiQzuRtLpL+iuGE3aalVaYecC9AUfQfrOJL1eRtC2hsvrmPRfgHBZ/MXe75A==} engines: {node: '>=18'} @@ -2906,9 +3296,21 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -2920,6 +3322,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-require-extensions@3.0.1: + resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} + engines: {node: '>=8'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2947,6 +3357,10 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -2957,6 +3371,9 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + diffable-html@4.1.0: + resolution: {integrity: sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2978,6 +3395,21 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} + + domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + + domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -2995,6 +3427,10 @@ packages: electron-to-chromium@1.5.155: resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -3011,6 +3447,12 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + entities@6.0.0: resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} engines: {node: '>=0.12'} @@ -3069,6 +3511,9 @@ packages: es-toolkit@1.38.0: resolution: {integrity: sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA==} + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -3180,6 +3625,10 @@ packages: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3188,6 +3637,17 @@ packages: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expand-tilde@1.2.2: + resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==} + engines: {node: '>=0.10.0'} + + expect-playwright@0.8.0: + resolution: {integrity: sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==} + expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} @@ -3234,6 +3694,9 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@1.2.0: resolution: {integrity: sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==} @@ -3268,6 +3731,22 @@ packages: resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} engines: {node: '>=6'} + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + + find-file-up@0.1.3: + resolution: {integrity: sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==} + engines: {node: '>=0.10.0'} + + find-pkg@0.1.2: + resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==} + engines: {node: '>=0.10.0'} + + find-process@1.4.10: + resolution: {integrity: sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==} + hasBin: true + find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -3295,6 +3774,15 @@ packages: resolution: {integrity: sha512-o1GuBHfApKnqxoPEiZhsz5Gj8W+kn+xaKFPwjPafKN0a876FMRB6VXyRqPmzfHVPWHeWwGUEzYeV806SV9FzCQ==} engines: {node: '>=0.4.0'} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -3317,6 +3805,13 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fromentries@1.3.2: + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} + + fs-exists-sync@0.1.0: + resolution: {integrity: sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==} + engines: {node: '>=0.10.0'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -3379,6 +3874,10 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3387,6 +3886,10 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -3434,6 +3937,14 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} + global-modules@0.2.3: + resolution: {integrity: sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==} + engines: {node: '>=0.10.0'} + + global-prefix@0.1.5: + resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} + engines: {node: '>=0.10.0'} + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -3468,6 +3979,10 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3487,10 +4002,18 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -3498,6 +4021,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -3518,6 +4044,10 @@ packages: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -3550,6 +4080,11 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} @@ -3568,6 +4103,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} @@ -3648,6 +4186,10 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -3746,6 +4288,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-windows@0.2.0: + resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} + engines: {node: '>=0.10.0'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -3771,10 +4317,34 @@ packages: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} + istanbul-lib-hook@3.0.0: + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} + + istanbul-lib-instrument@4.0.3: + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-processinfo@2.0.3: + resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} + engines: {node: '>=8'} + istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + istanbul-reports@3.1.7: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} @@ -3786,14 +4356,68 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-junit@16.0.0: + resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} + engines: {node: '>=10.12.0'} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@29.7.0: resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3802,10 +4426,89 @@ packages: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-playwright-preset@4.0.0: + resolution: {integrity: sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==} + peerDependencies: + jest: ^29.3.1 + jest-circus: ^29.3.1 + jest-environment-node: ^29.3.1 + jest-runner: ^29.3.1 + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-process-manager@0.4.0: + resolution: {integrity: sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==} + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-serializer-html@7.1.0: + resolution: {integrity: sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watch-typeahead@2.2.2: + resolution: {integrity: sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==} + engines: {node: ^14.17.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + jest: ^27.0.0 || ^28.0.0 || ^29.0.0 + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -3814,6 +4517,9 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3889,6 +4595,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -3907,6 +4616,10 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + junit-report-builder@5.1.1: + resolution: {integrity: sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==} + engines: {node: '>=16'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3981,6 +4694,9 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -4023,6 +4739,10 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -4059,10 +4779,17 @@ packages: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + map-or-similar@1.5.0: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} @@ -4153,6 +4880,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -4178,6 +4909,13 @@ packages: node-fetch-native@1.6.6: resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-preload@0.2.1: + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -4200,6 +4938,11 @@ packages: nwsapi@2.2.20: resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nyc@15.1.0: + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} + hasBin: true + nypm@0.5.4: resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} engines: {node: ^14.16.0 || >=16.10.0} @@ -4260,6 +5003,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + os-homedir@1.0.2: + resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} + engines: {node: '>=0.10.0'} + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -4318,6 +5065,10 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} + p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -4326,6 +5077,10 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-hash@4.0.0: + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -4340,6 +5095,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -4437,6 +5196,10 @@ packages: resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} engines: {node: '>=6'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -4533,6 +5296,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + process-on-spawn@1.1.0: + resolution: {integrity: sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==} + engines: {node: '>=8'} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -4547,6 +5314,9 @@ packages: proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -4554,6 +5324,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -4644,6 +5417,10 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -4682,6 +5459,10 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + release-zalgo@1.0.0: + resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} + engines: {node: '>=4'} + request-progress@3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} @@ -4693,6 +5474,17 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-dir@0.1.1: + resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4701,6 +5493,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -4790,6 +5586,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4814,6 +5613,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -4871,6 +5674,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -4878,6 +5684,13 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spawn-wrap@2.0.0: + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} + + spawnd@5.0.0: + resolution: {integrity: sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} @@ -4916,6 +5729,14 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-length@5.0.1: + resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} + engines: {node: '>=12.20'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4947,6 +5768,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4959,6 +5783,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -4987,6 +5815,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5103,6 +5935,9 @@ packages: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -5155,6 +5990,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + type-detect@4.1.0: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} @@ -5163,6 +6002,14 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -5179,6 +6026,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + typescript-eslint@8.32.1: resolution: {integrity: sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5393,9 +6243,27 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wait-on@7.2.0: + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + wait-on@8.0.3: + resolution: {integrity: sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==} + engines: {node: '>=12.0.0'} + hasBin: true + + wait-port@0.2.14: + resolution: {integrity: sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==} + engines: {node: '>=8'} + hasBin: true + walk-up-path@3.0.1: resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -5427,10 +6295,17 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5467,6 +6342,13 @@ packages: write-file-atomic@2.4.3: resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} + write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -5483,9 +6365,19 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -5501,6 +6393,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -5509,6 +6405,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -5763,6 +6663,26 @@ snapshots: dependencies: '@babel/core': 7.27.1 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -5778,11 +6698,61 @@ snapshots: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -6793,6 +7763,12 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + '@hookform/resolvers@5.0.1(react-hook-form@7.56.4(react@19.1.0))': dependencies: '@standard-schema/utils': 0.3.0 @@ -6820,15 +7796,172 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + '@istanbuljs/schema@0.1.3': {} - '@jest/expect-utils@29.7.0': + '@jest/console@29.7.0': dependencies: - jest-get-type: 29.6.3 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 - '@jest/schemas@29.6.3': + '@jest/core@29.7.0': dependencies: - '@sinclair/typebox': 0.27.8 + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.15.19) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.15.19 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 22.15.19 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.27.1 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color '@jest/types@29.6.3': dependencies: @@ -7253,10 +8386,26 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.41.0': optional: true + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + '@sinclair/typebox@0.27.8': {} '@sindresorhus/merge-streams@2.3.0': {} + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@standard-schema/utils@0.3.0': {} '@storybook/addon-a11y@8.6.14(storybook@8.6.14(prettier@3.5.3))': @@ -7461,6 +8610,10 @@ snapshots: storybook: 8.6.14(prettier@3.5.3) unplugin: 1.16.1 + '@storybook/csf@0.1.13': + dependencies: + type-fest: 2.19.0 + '@storybook/global@5.0.0': {} '@storybook/icons@1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': @@ -7525,6 +8678,37 @@ snapshots: '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) typescript: 5.8.3 + '@storybook/test-runner@0.22.0(@types/node@22.15.19)(storybook@8.6.14(prettier@3.5.3))': + dependencies: + '@babel/core': 7.27.1 + '@babel/generator': 7.27.1 + '@babel/template': 7.27.2 + '@babel/types': 7.27.1 + '@jest/types': 29.6.3 + '@storybook/csf': 0.1.13 + '@swc/core': 1.11.24 + '@swc/jest': 0.2.38(@swc/core@1.11.24) + expect-playwright: 0.8.0 + jest: 29.7.0(@types/node@22.15.19) + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-junit: 16.0.0 + jest-playwright-preset: 4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@22.15.19)) + jest-runner: 29.7.0 + jest-serializer-html: 7.1.0 + jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@22.15.19)) + nyc: 15.1.0 + playwright: 1.52.0 + storybook: 8.6.14(prettier@3.5.3) + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - babel-plugin-macros + - debug + - node-notifier + - supports-color + - ts-node + '@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3))': dependencies: '@storybook/global': 5.0.0 @@ -7544,6 +8728,65 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.5.3) + '@swc/core-darwin-arm64@1.11.24': + optional: true + + '@swc/core-darwin-x64@1.11.24': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.11.24': + optional: true + + '@swc/core-linux-arm64-gnu@1.11.24': + optional: true + + '@swc/core-linux-arm64-musl@1.11.24': + optional: true + + '@swc/core-linux-x64-gnu@1.11.24': + optional: true + + '@swc/core-linux-x64-musl@1.11.24': + optional: true + + '@swc/core-win32-arm64-msvc@1.11.24': + optional: true + + '@swc/core-win32-ia32-msvc@1.11.24': + optional: true + + '@swc/core-win32-x64-msvc@1.11.24': + optional: true + + '@swc/core@1.11.24': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.21 + optionalDependencies: + '@swc/core-darwin-arm64': 1.11.24 + '@swc/core-darwin-x64': 1.11.24 + '@swc/core-linux-arm-gnueabihf': 1.11.24 + '@swc/core-linux-arm64-gnu': 1.11.24 + '@swc/core-linux-arm64-musl': 1.11.24 + '@swc/core-linux-x64-gnu': 1.11.24 + '@swc/core-linux-x64-musl': 1.11.24 + '@swc/core-win32-arm64-msvc': 1.11.24 + '@swc/core-win32-ia32-msvc': 1.11.24 + '@swc/core-win32-x64-msvc': 1.11.24 + + '@swc/counter@0.1.3': {} + + '@swc/jest@0.2.38(@swc/core@1.11.24)': + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@swc/core': 1.11.24 + '@swc/counter': 0.1.3 + jsonc-parser: 3.3.1 + + '@swc/types@0.1.21': + dependencies: + '@swc/counter': 0.1.3 + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.27.1 @@ -7624,6 +8867,10 @@ snapshots: '@types/estree@1.0.7': {} + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.15.19 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -7641,6 +8888,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/junit-report-builder@3.0.2': {} + '@types/mdx@2.0.13': {} '@types/node@12.20.55': {} @@ -7683,6 +8932,10 @@ snapshots: '@types/uuid@9.0.8': {} + '@types/wait-on@5.3.4': + dependencies: + '@types/node': 22.15.19 + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -7934,6 +9187,8 @@ snapshots: dependencies: type-fest: 0.21.3 + ansi-escapes@6.2.1: {} + ansi-escapes@7.0.0: dependencies: environment: 1.1.0 @@ -7942,6 +9197,10 @@ snapshots: ansi-regex@6.1.0: {} + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -7957,8 +9216,14 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + append-transform@2.0.0: + dependencies: + default-require-extensions: 3.0.1 + arch@2.2.0: {} + archy@1.0.0: {} + arg@5.0.2: {} argparse@1.0.10: @@ -8080,10 +9345,62 @@ snapshots: axe-core@4.10.3: {} + axe-html-reporter@2.2.11(axe-core@4.10.3): + dependencies: + axe-core: 4.10.3 + mustache: 4.2.0 + + axe-playwright@2.1.0(playwright@1.52.0): + dependencies: + '@types/junit-report-builder': 3.0.2 + axe-core: 4.10.3 + axe-html-reporter: 2.2.11(axe-core@4.10.3) + junit-report-builder: 5.1.1 + picocolors: 1.1.1 + playwright: 1.52.0 + + axios@1.9.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-core@7.0.0-bridge.0(@babel/core@7.27.1): dependencies: '@babel/core': 7.27.1 + babel-jest@29.7.0(@babel/core@7.27.1): + dependencies: + '@babel/core': 7.27.1 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.27.1) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.1 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.7 + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.27.1): dependencies: '@babel/compat-data': 7.27.2 @@ -8108,6 +9425,31 @@ snapshots: transitivePeerDependencies: - supports-color + babel-preset-current-node-syntax@1.1.0(@babel/core@7.27.1): + dependencies: + '@babel/core': 7.27.1 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.27.1) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.27.1) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.27.1) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.1) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.1) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.27.1) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.27.1) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.27.1) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.27.1) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.27.1) + + babel-preset-jest@29.6.3(@babel/core@7.27.1): + dependencies: + '@babel/core': 7.27.1 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.1) + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -8152,6 +9494,10 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.5) + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} @@ -8180,6 +9526,13 @@ snapshots: cachedir@2.4.0: {} + caching-transform@4.0.0: + dependencies: + hasha: 5.2.2 + make-dir: 3.1.0 + package-hash: 4.0.0 + write-file-atomic: 3.0.3 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -8201,6 +9554,10 @@ snapshots: camelcase-css@2.0.1: {} + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001718: {} caseless@0.12.0: {} @@ -8223,6 +9580,12 @@ snapshots: loupe: 3.1.3 pathval: 2.0.0 + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -8235,6 +9598,10 @@ snapshots: chalk@5.4.1: {} + char-regex@1.0.2: {} + + char-regex@2.0.2: {} + chardet@0.7.0: {} check-error@1.0.3: @@ -8269,6 +9636,8 @@ snapshots: dependencies: consola: 3.4.2 + cjs-module-lexer@1.4.3: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -8299,6 +9668,12 @@ snapshots: slice-ansi: 5.0.0 string-width: 7.2.0 + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -8319,10 +9694,20 @@ snapshots: clsx@2.1.1: {} + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} colorette@2.0.20: {} @@ -8338,6 +9723,8 @@ snapshots: commander@13.1.0: {} + commander@3.0.2: {} + commander@4.1.1: {} commander@6.2.1: {} @@ -8361,6 +9748,16 @@ snapshots: concat-map@0.0.1: {} + concurrently@9.1.2: + dependencies: + chalk: 4.1.2 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + confbox@0.1.8: {} consola@3.4.2: {} @@ -8380,6 +9777,8 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} core-js-compat@3.42.0: @@ -8404,6 +9803,21 @@ snapshots: optionalDependencies: typescript: 5.8.3 + create-jest@29.7.0(@types/node@22.15.19): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.15.19) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-storybook@8.6.14: dependencies: recast: 0.23.11 @@ -8426,6 +9840,11 @@ snapshots: csstype@3.1.3: {} + cwd@0.10.0: + dependencies: + find-pkg: 0.1.2 + fs-exists-sync: 0.1.0 + cypress-visual-regression@5.3.0(cypress@14.3.3): dependencies: chalk: 4.1.2 @@ -8525,8 +9944,12 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + decamelize@1.2.0: {} + decimal.js@10.5.0: {} + dedent@1.6.0: {} + deep-eql@4.1.4: dependencies: type-detect: 4.1.0 @@ -8535,6 +9958,12 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + + default-require-extensions@3.0.1: + dependencies: + strip-bom: 4.0.0 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -8557,12 +9986,18 @@ snapshots: detect-indent@6.1.0: {} + detect-newline@3.1.0: {} + detect-node-es@1.1.0: {} didyoumean@1.2.2: {} diff-sequences@29.6.3: {} + diffable-html@4.1.0: + dependencies: + htmlparser2: 3.10.1 + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -8581,6 +10016,24 @@ snapshots: dom-accessibility-api@0.6.3: {} + dom-serializer@0.2.2: + dependencies: + domelementtype: 2.3.0 + entities: 2.2.0 + + domelementtype@1.3.1: {} + + domelementtype@2.3.0: {} + + domhandler@2.4.2: + dependencies: + domelementtype: 1.3.1 + + domutils@1.7.0: + dependencies: + dom-serializer: 0.2.2 + domelementtype: 1.3.1 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -8600,6 +10053,8 @@ snapshots: electron-to-chromium@1.5.155: {} + emittery@0.13.1: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -8615,6 +10070,10 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@1.1.2: {} + + entities@2.2.0: {} + entities@6.0.0: {} env-paths@2.2.1: {} @@ -8729,6 +10188,8 @@ snapshots: es-toolkit@1.38.0: {} + es6-error@4.1.1: {} + esbuild-register@3.6.0(esbuild@0.25.4): dependencies: debug: 4.4.1(supports-color@8.1.1) @@ -8921,6 +10382,18 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -8937,6 +10410,14 @@ snapshots: dependencies: pify: 2.3.0 + exit@0.1.2: {} + + expand-tilde@1.2.2: + dependencies: + os-homedir: 1.0.2 + + expect-playwright@0.8.0: {} + expect-type@1.2.1: {} expect@29.7.0: @@ -8989,6 +10470,10 @@ snapshots: dependencies: reusify: 1.1.0 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + fd-package-json@1.2.0: dependencies: walk-up-path: 3.0.1 @@ -9021,6 +10506,27 @@ snapshots: make-dir: 2.1.0 pkg-dir: 3.0.0 + find-cache-dir@3.3.2: + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + find-file-up@0.1.3: + dependencies: + fs-exists-sync: 0.1.0 + resolve-dir: 0.1.1 + + find-pkg@0.1.2: + dependencies: + find-file-up: 0.1.3 + + find-process@1.4.10: + dependencies: + chalk: 4.1.2 + commander: 12.1.0 + loglevel: 1.9.2 + find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -9050,6 +10556,8 @@ snapshots: flow-parser@0.271.0: {} + follow-redirects@1.15.9: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -9075,6 +10583,10 @@ snapshots: fraction.js@4.3.7: {} + fromentries@1.3.2: {} + + fs-exists-sync@0.1.0: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -9142,6 +10654,8 @@ snapshots: get-nonce@1.0.1: {} + get-package-type@0.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -9151,6 +10665,8 @@ snapshots: dependencies: pump: 3.0.2 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-symbol-description@1.1.0: @@ -9217,6 +10733,18 @@ snapshots: dependencies: ini: 2.0.0 + global-modules@0.2.3: + dependencies: + global-prefix: 0.1.5 + is-windows: 0.2.0 + + global-prefix@0.1.5: + dependencies: + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 0.2.0 + which: 1.3.1 + globals@11.12.0: {} globals@14.0.0: {} @@ -9252,6 +10780,8 @@ snapshots: has-bigints@1.1.0: {} + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -9268,16 +10798,34 @@ snapshots: dependencies: has-symbols: 1.1.0 + hasha@5.2.2: + dependencies: + is-stream: 2.0.1 + type-fest: 0.8.1 + hasown@2.0.2: dependencies: function-bind: 1.1.2 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 html-escaper@2.0.2: {} + htmlparser2@3.10.1: + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -9302,6 +10850,8 @@ snapshots: human-signals@1.1.1: {} + human-signals@2.1.0: {} + human-signals@5.0.0: {} husky@8.0.3: {} @@ -9325,6 +10875,11 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + import-meta-resolve@4.1.0: {} imurmurhash@0.1.4: {} @@ -9338,6 +10893,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + ini@2.0.0: {} ini@4.1.1: {} @@ -9415,6 +10972,8 @@ snapshots: dependencies: get-east-asian-width: 1.3.0 + is-generator-fn@2.1.0: {} + is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -9505,6 +11064,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-windows@0.2.0: {} + is-windows@1.0.2: {} is-wsl@2.2.0: @@ -9521,12 +11082,62 @@ snapshots: istanbul-lib-coverage@3.2.2: {} + istanbul-lib-hook@3.0.0: + dependencies: + append-transform: 2.0.0 + + istanbul-lib-instrument@4.0.3: + dependencies: + '@babel/core': 7.27.1 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.27.1 + '@babel/parser': 7.27.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.27.1 + '@babel/parser': 7.27.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-processinfo@2.0.3: + dependencies: + archy: 1.0.0 + cross-spawn: 7.0.6 + istanbul-lib-coverage: 3.2.2 + p-map: 3.0.0 + rimraf: 3.0.2 + uuid: 8.3.2 + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 @@ -9547,6 +11158,87 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.6.0 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@22.15.19): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.15.19) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.15.19) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@22.15.19): + dependencies: + '@babel/core': 7.27.1 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.1) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.15.19 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -9554,8 +11246,57 @@ snapshots: jest-get-type: 29.6.3 pretty-format: 29.7.0 - jest-get-type@29.6.3: {} - + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.15.19 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-junit@16.0.0: + dependencies: + mkdirp: 1.0.4 + strip-ansi: 6.0.1 + uuid: 8.3.2 + xml: 1.0.1 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + jest-matcher-utils@29.7.0: dependencies: chalk: 4.1.2 @@ -9575,6 +11316,151 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + jest-util: 29.7.0 + + jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@22.15.19)): + dependencies: + expect-playwright: 0.8.0 + jest: 29.7.0(@types/node@22.15.19) + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-process-manager: 0.4.0 + jest-runner: 29.7.0 + nyc: 15.1.0 + playwright-core: 1.52.0 + rimraf: 3.0.2 + uuid: 8.3.2 + transitivePeerDependencies: + - debug + - supports-color + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-process-manager@0.4.0: + dependencies: + '@types/wait-on': 5.3.4 + chalk: 4.1.2 + cwd: 0.10.0 + exit: 0.1.2 + find-process: 1.4.10 + prompts: 2.4.2 + signal-exit: 3.0.7 + spawnd: 5.0.0 + tree-kill: 1.2.2 + wait-on: 7.2.0 + transitivePeerDependencies: + - debug + - supports-color + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-serializer-html@7.1.0: + dependencies: + diffable-html: 4.1.0 + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.27.1 + '@babel/generator': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.1) + '@babel/types': 7.27.1 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.1) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 @@ -9584,10 +11470,68 @@ snapshots: graceful-fs: 4.2.11 picomatch: 2.3.1 + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@22.15.19)): + dependencies: + ansi-escapes: 6.2.1 + chalk: 5.4.1 + jest: 29.7.0(@types/node@22.15.19) + jest-regex-util: 29.6.3 + jest-watcher: 29.7.0 + slash: 5.1.0 + string-length: 5.0.1 + strip-ansi: 7.1.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.19 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.15.19 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@22.15.19): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.15.19) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jiti@1.21.7: {} jiti@2.4.2: {} + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -9679,6 +11623,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -9705,6 +11651,12 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + junit-report-builder@5.1.1: + dependencies: + lodash: 4.17.21 + make-dir: 3.1.0 + xmlbuilder: 15.1.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -9789,6 +11741,8 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.flattendeep@4.4.0: {} + lodash.isplainobject@4.0.6: {} lodash.kebabcase@4.1.1: {} @@ -9829,6 +11783,8 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 + loglevel@1.9.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -9864,10 +11820,18 @@ snapshots: pify: 4.0.1 semver: 5.7.2 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + make-dir@4.0.0: dependencies: semver: 7.7.2 + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + map-or-similar@1.5.0: {} math-intrinsics@1.1.0: {} @@ -9937,6 +11901,8 @@ snapshots: ms@2.1.3: {} + mustache@4.2.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -9957,6 +11923,12 @@ snapshots: node-fetch-native@1.6.6: {} + node-int64@0.4.0: {} + + node-preload@0.2.1: + dependencies: + process-on-spawn: 1.1.0 + node-releases@2.0.19: {} normalize-path@3.0.0: {} @@ -9973,6 +11945,38 @@ snapshots: nwsapi@2.2.20: {} + nyc@15.1.0: + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + caching-transform: 4.0.0 + convert-source-map: 1.9.0 + decamelize: 1.2.0 + find-cache-dir: 3.3.2 + find-up: 4.1.0 + foreground-child: 2.0.0 + get-package-type: 0.1.0 + glob: 7.2.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-hook: 3.0.0 + istanbul-lib-instrument: 4.0.3 + istanbul-lib-processinfo: 2.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + make-dir: 3.1.0 + node-preload: 0.2.1 + p-map: 3.0.0 + process-on-spawn: 1.1.0 + resolve-from: 5.0.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + spawn-wrap: 2.0.0 + test-exclude: 6.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - supports-color + nypm@0.5.4: dependencies: citty: 0.1.6 @@ -10051,6 +12055,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + os-homedir@1.0.2: {} + os-tmpdir@1.0.2: {} ospath@1.2.2: {} @@ -10105,12 +12111,23 @@ snapshots: p-map@2.1.0: {} + p-map@3.0.0: + dependencies: + aggregate-error: 3.1.0 + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 p-try@2.2.0: {} + package-hash@4.0.0: + dependencies: + graceful-fs: 4.2.11 + hasha: 5.2.2 + lodash.flattendeep: 4.4.0 + release-zalgo: 1.0.0 + package-json-from-dist@1.0.1: {} package-manager-detector@0.2.11: @@ -10128,6 +12145,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-passwd@1.0.0: {} + parse5@7.3.0: dependencies: entities: 6.0.0 @@ -10189,6 +12208,10 @@ snapshots: dependencies: find-up: 3.0.0 + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -10270,6 +12293,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + process-on-spawn@1.1.0: + dependencies: + fromentries: 1.3.2 + process@0.11.10: {} prompts@2.4.2: @@ -10285,6 +12312,8 @@ snapshots: proxy-from-env@1.0.0: {} + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -10292,6 +12321,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -10381,6 +12412,12 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -10439,6 +12476,10 @@ snapshots: dependencies: jsesc: 3.0.2 + release-zalgo@1.0.0: + dependencies: + es6-error: 4.1.1 + request-progress@3.0.0: dependencies: throttleit: 1.0.1 @@ -10447,10 +12488,23 @@ snapshots: require-from-string@2.0.2: {} + require-main-filename@2.0.0: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-dir@0.1.1: + dependencies: + expand-tilde: 1.2.2 + global-modules: 0.2.3 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} + resolve.exports@2.0.3: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -10560,6 +12614,8 @@ snapshots: semver@7.7.2: {} + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -10592,6 +12648,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.2: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -10656,6 +12714,11 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -10663,6 +12726,24 @@ snapshots: source-map@0.6.1: {} + spawn-wrap@2.0.0: + dependencies: + foreground-child: 2.0.0 + is-windows: 1.0.2 + make-dir: 3.1.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + which: 2.0.2 + + spawnd@5.0.0: + dependencies: + exit: 0.1.2 + signal-exit: 3.0.7 + tree-kill: 1.2.2 + wait-port: 0.2.14 + transitivePeerDependencies: + - supports-color + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 @@ -10704,6 +12785,16 @@ snapshots: string-argv@0.3.2: {} + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-length@5.0.1: + dependencies: + char-regex: 2.0.2 + strip-ansi: 7.1.0 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -10766,6 +12857,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -10776,6 +12871,8 @@ snapshots: strip-bom@3.0.0: {} + strip-bom@4.0.0: {} + strip-final-newline@2.0.0: {} strip-final-newline@3.0.0: {} @@ -10804,6 +12901,10 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -10921,6 +13022,8 @@ snapshots: tmp@0.2.3: {} + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -10967,10 +13070,16 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + type-detect@4.1.0: {} type-fest@0.21.3: {} + type-fest@0.8.1: {} + + type-fest@2.19.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -11004,6 +13113,10 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typedarray-to-buffer@3.1.5: + dependencies: + is-typedarray: 1.0.0 + typescript-eslint@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3): dependencies: '@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3) @@ -11227,8 +13340,40 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wait-on@7.2.0: + dependencies: + axios: 1.9.0 + joi: 17.13.3 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + + wait-on@8.0.3: + dependencies: + axios: 1.9.0 + joi: 17.13.3 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + + wait-port@0.2.14: + dependencies: + chalk: 2.4.2 + commander: 3.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + walk-up-path@3.0.1: {} + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + webidl-conversions@7.0.0: {} webpack-virtual-modules@0.6.2: {} @@ -11275,6 +13420,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -11285,6 +13432,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -11328,12 +13479,30 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + write-file-atomic@3.0.3: + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + ws@8.18.2: {} xml-name-validator@5.0.0: {} + xml@1.0.1: {} + + xmlbuilder@15.1.1: {} + xmlchars@2.2.0: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -11342,10 +13511,29 @@ snapshots: yaml@2.8.0: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@16.2.0: dependencies: cliui: 7.0.4 From 4853c814a446f45e4a622b48365f44eb35c6247a Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 21:57:57 +0200 Subject: [PATCH 02/13] fix: make a11y testing more robust with dynamic port detection --- .github/workflows/a11y-test.yml | 4 +- packages/ui-kit/.storybook/preview.tsx | 2 +- packages/ui-kit/.storybook/test-runner.ts | 8 +- packages/ui-kit/package.json | 2 +- packages/ui-kit/scripts/run-storybook-test.js | 123 ++++++++++++++++++ 5 files changed, 133 insertions(+), 6 deletions(-) create mode 100755 packages/ui-kit/scripts/run-storybook-test.js diff --git a/.github/workflows/a11y-test.yml b/.github/workflows/a11y-test.yml index eee9193..1ca2cd9 100644 --- a/.github/workflows/a11y-test.yml +++ b/.github/workflows/a11y-test.yml @@ -26,4 +26,6 @@ jobs: run: cd packages/ui-kit && npx playwright install --with-deps - name: Run accessibility tests with Storybook - run: cd packages/ui-kit && pnpm test-storybook:ci \ No newline at end of file + run: cd packages/ui-kit && pnpm test-storybook:ci + env: + CI: true \ No newline at end of file diff --git a/packages/ui-kit/.storybook/preview.tsx b/packages/ui-kit/.storybook/preview.tsx index 483353a..e0a3bbb 100644 --- a/packages/ui-kit/.storybook/preview.tsx +++ b/packages/ui-kit/.storybook/preview.tsx @@ -25,7 +25,7 @@ const preview: Preview = { }, a11y: { // axe-core configuration options - element: '#root', + element: '#storybook-root', config: {}, disable: false, }, diff --git a/packages/ui-kit/.storybook/test-runner.ts b/packages/ui-kit/.storybook/test-runner.ts index d12cba0..ecbf7e1 100644 --- a/packages/ui-kit/.storybook/test-runner.ts +++ b/packages/ui-kit/.storybook/test-runner.ts @@ -3,7 +3,8 @@ import { injectAxe, checkA11y } from 'axe-playwright' import { getStoryContext } from '@storybook/test-runner' const config: TestRunnerConfig = { - async preVisit(page) { + async preVisit(page, context) { + console.log(`Testing story: ${context.id}`); await injectAxe(page) }, async postVisit(page, context) { @@ -24,8 +25,9 @@ const config: TestRunnerConfig = { }, axeConfig) } - // Use the element specified in parameters or default to #root - const element = storyContext.parameters?.a11y?.element || '#root' + // Use the element specified in parameters or default to #storybook-root + // The selector #storybook-root is more reliable than #root + const element = storyContext.parameters?.a11y?.element || '#storybook-root' // Run the accessibility tests await checkA11y(page, element, { diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index 76f2b04..43c48a2 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -26,7 +26,7 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "test-storybook": "test-storybook", - "test-storybook:ci": "concurrently -k -s first \"storybook dev --ci --port 6006\" \"wait-on tcp:6006 && test-storybook\"", + "test-storybook:ci": "node scripts/run-storybook-test.js", "cy:open": "cypress open", "cy:run": "cypress run", "theme-screenshots": "cypress run --spec \"cypress/e2e/theme.cy.ts\"", diff --git a/packages/ui-kit/scripts/run-storybook-test.js b/packages/ui-kit/scripts/run-storybook-test.js new file mode 100755 index 0000000..1ff507a --- /dev/null +++ b/packages/ui-kit/scripts/run-storybook-test.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node +/* eslint-disable no-console, no-undef */ + +import { spawn, exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +// Script automatically detects the port Storybook is running on + +/** + * Start Storybook and detect the port it's running on + */ +async function startStorybook() { + console.log('Starting Storybook...'); + + // Start Storybook with --ci flag + const storybookProcess = spawn('storybook', ['dev', '--ci'], { + shell: true, + stdio: ['pipe', 'pipe', 'pipe'] + }); + + let port = null; + + // Set up timeout for port detection + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('Timeout waiting for Storybook to start')); + }, 60000); // 60 second timeout + }); + + // Create a promise that resolves when we detect the port + const portDetectionPromise = new Promise((resolve) => { + storybookProcess.stdout.on('data', (data) => { + const output = data.toString(); + console.log(`Storybook: ${output}`); + + // Look for the port in the output + const portMatch = output.match(/Local:\s*http:\/\/localhost:(\d+)/); + if (portMatch && portMatch[1]) { + port = parseInt(portMatch[1], 10); + console.log(`Detected Storybook running on port: ${port}`); + resolve(port); + } + }); + + storybookProcess.stderr.on('data', (data) => { + console.error(`Storybook stderr: ${data}`); + }); + }); + + // Wait for port detection or timeout + try { + port = await Promise.race([portDetectionPromise, timeoutPromise]); + } catch (error) { + console.error('Error detecting Storybook port:', error); + process.exit(1); + } + + return { storybookProcess, port }; +} + +/** + * Run the test-storybook command with the detected port + */ +async function runTests(port) { + console.log(`Waiting for Storybook to be fully ready on port ${port}...`); + + try { + // Wait for Storybook server to be ready + await execAsync(`wait-on tcp:${port} -t 60000`); + console.log('Storybook is ready. Running accessibility tests...'); + + // Run the tests against the detected port + const testProcess = spawn('test-storybook', [`--url=http://localhost:${port}`], { + shell: true, + stdio: 'inherit' + }); + + return new Promise((resolve, reject) => { + testProcess.on('close', (code) => { + if (code !== 0) { + reject(new Error(`test-storybook exited with code ${code}`)); + } else { + resolve(); + } + }); + }); + } catch (error) { + console.error('Error running tests:', error); + throw error; + } +} + +/** + * Main function to orchestrate the process + */ +async function main() { + let storybookProcess = null; + + try { + // Start Storybook and detect port + const storybook = await startStorybook(); + storybookProcess = storybook.storybookProcess; + + // Run tests against the detected port + await runTests(storybook.port); + + console.log('All tests completed successfully'); + process.exit(0); + } catch (error) { + console.error('Error in test process:', error); + process.exit(1); + } finally { + // Ensure Storybook is terminated + if (storybookProcess) { + storybookProcess.kill(); + } + } +} + +// Run the main function +main(); \ No newline at end of file From be54c82335146eaa185853a8ba3a872a6575053a Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 21:59:54 +0200 Subject: [PATCH 03/13] test: add component with severe a11y violations --- .../test/A11yViolatingForm.stories.tsx | 17 +++++++ .../src/components/test/A11yViolatingForm.tsx | 48 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 packages/ui-kit/src/components/test/A11yViolatingForm.stories.tsx create mode 100644 packages/ui-kit/src/components/test/A11yViolatingForm.tsx diff --git a/packages/ui-kit/src/components/test/A11yViolatingForm.stories.tsx b/packages/ui-kit/src/components/test/A11yViolatingForm.stories.tsx new file mode 100644 index 0000000..0494794 --- /dev/null +++ b/packages/ui-kit/src/components/test/A11yViolatingForm.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { A11yViolatingForm } from './A11yViolatingForm'; + +const meta: Meta = { + title: 'Test/A11yViolatingForm', + component: A11yViolatingForm, + parameters: { + // No a11y parameters - should trigger violations in CI + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {} +}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/test/A11yViolatingForm.tsx b/packages/ui-kit/src/components/test/A11yViolatingForm.tsx new file mode 100644 index 0000000..7d18b2f --- /dev/null +++ b/packages/ui-kit/src/components/test/A11yViolatingForm.tsx @@ -0,0 +1,48 @@ +// This component deliberately violates multiple accessibility guidelines +export const A11yViolatingForm = () => { + return ( +
+

Contact Form

+ +
+ {/* Missing label */} + +
+ +
+ {/* Missing label and non-descriptive placeholder */} + +
+ +
+ {/* Missing alt text on image */} + +
+ +
+ {/* Insufficient color contrast */} + +
+ + {/* Empty heading */} +

+ + {/* Incorrect heading hierarchy (skipping h3) */} +

Details

+ + {/* Non-semantic button */} +
alert('Clicked')} + style={{ + cursor: 'pointer', + padding: '10px', + backgroundColor: 'blue' + }} + > + Click me +
+
+ ); +}; \ No newline at end of file From 02cf8b912023f50ce69d028a0658511c7909cd9d Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 22:09:38 +0200 Subject: [PATCH 04/13] fix: improve storybook test runner with better timeout handling --- packages/ui-kit/scripts/run-storybook-test.js | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/packages/ui-kit/scripts/run-storybook-test.js b/packages/ui-kit/scripts/run-storybook-test.js index 1ff507a..c9458f6 100755 --- a/packages/ui-kit/scripts/run-storybook-test.js +++ b/packages/ui-kit/scripts/run-storybook-test.js @@ -8,14 +8,22 @@ const execAsync = promisify(exec); // Script automatically detects the port Storybook is running on +// Configuration +const STORYBOOK_TIMEOUT_MS = 120000; // 2 minutes +const TEST_TIMEOUT_MS = 120000; // 2 minutes +const READY_WAIT_MS = 10000; // Additional wait time after port detection + /** * Start Storybook and detect the port it's running on */ async function startStorybook() { console.log('Starting Storybook...'); - // Start Storybook with --ci flag - const storybookProcess = spawn('storybook', ['dev', '--ci'], { + // Start Storybook with --ci flag and specify port range + // Using a random port in the 60000-65000 range to avoid conflicts + const randomPort = Math.floor(Math.random() * 5000) + 60000; + + const storybookProcess = spawn('storybook', ['dev', '--ci', '--port', randomPort.toString()], { shell: true, stdio: ['pipe', 'pipe', 'pipe'] }); @@ -25,8 +33,8 @@ async function startStorybook() { // Set up timeout for port detection const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { - reject(new Error('Timeout waiting for Storybook to start')); - }, 60000); // 60 second timeout + reject(new Error(`Timeout waiting for Storybook to start (${STORYBOOK_TIMEOUT_MS / 1000} seconds)`)); + }, STORYBOOK_TIMEOUT_MS); }); // Create a promise that resolves when we detect the port @@ -35,18 +43,34 @@ async function startStorybook() { const output = data.toString(); console.log(`Storybook: ${output}`); - // Look for the port in the output + // Look for the port in the output - try both patterns that Storybook might use const portMatch = output.match(/Local:\s*http:\/\/localhost:(\d+)/); + const altPortMatch = output.match(/http:\/\/127.0.0.1:(\d+)/); + if (portMatch && portMatch[1]) { port = parseInt(portMatch[1], 10); console.log(`Detected Storybook running on port: ${port}`); resolve(port); + } else if (altPortMatch && altPortMatch[1]) { + port = parseInt(altPortMatch[1], 10); + console.log(`Detected Storybook running on port: ${port}`); + resolve(port); } }); storybookProcess.stderr.on('data', (data) => { console.error(`Storybook stderr: ${data}`); }); + + storybookProcess.on('error', (error) => { + console.error(`Storybook process error: ${error.message}`); + }); + + storybookProcess.on('exit', (code) => { + if (code !== null && code !== 0) { + console.error(`Storybook process exited with code ${code}`); + } + }); }); // Wait for port detection or timeout @@ -54,9 +78,35 @@ async function startStorybook() { port = await Promise.race([portDetectionPromise, timeoutPromise]); } catch (error) { console.error('Error detecting Storybook port:', error); + + // Log the current system state + console.log('Checking system state...'); + try { + const { stdout: psOutput } = await execAsync('ps aux | grep storybook'); + console.log('Running storybook processes:'); + console.log(psOutput); + + const { stdout: netstatOutput } = await execAsync('netstat -tulpn 2>/dev/null | grep node'); + console.log('Node ports in use:'); + console.log(netstatOutput); + } catch { + console.error('Failed to check system state'); + } + + // Kill any orphaned storybook processes + try { + await execAsync('pkill -f "storybook dev"'); + } catch { + // Ignore errors if no processes found + } + process.exit(1); } + // Give extra time for Storybook to fully initialize + console.log(`Waiting additional ${READY_WAIT_MS / 1000} seconds for Storybook to fully initialize...`); + await new Promise(resolve => setTimeout(resolve, READY_WAIT_MS)); + return { storybookProcess, port }; } @@ -64,15 +114,33 @@ async function startStorybook() { * Run the test-storybook command with the detected port */ async function runTests(port) { - console.log(`Waiting for Storybook to be fully ready on port ${port}...`); + console.log(`Running tests on Storybook at port ${port}...`); try { // Wait for Storybook server to be ready - await execAsync(`wait-on tcp:${port} -t 60000`); + await execAsync(`wait-on tcp:${port} -t ${TEST_TIMEOUT_MS}`); console.log('Storybook is ready. Running accessibility tests...'); - // Run the tests against the detected port - const testProcess = spawn('test-storybook', [`--url=http://localhost:${port}`], { + // List available story IDs for debugging + try { + console.log('Fetching available stories...'); + const { stdout } = await execAsync(`curl -s http://localhost:${port}/index.json`); + const storyData = JSON.parse(stdout); + console.log(`Found ${Object.keys(storyData.entries).length} stories`); + Object.keys(storyData.entries).slice(0, 10).forEach(id => { + console.log(`- Story ID: ${id}`); + }); + } catch (err) { + console.warn('Failed to fetch story list:', err.message); + } + + // Run the tests against the detected port with additional flags + const testProcess = spawn('test-storybook', [ + `--url=http://localhost:${port}`, + '--maxWorkers=2', + '--watchAll=false', + '--ci' + ], { shell: true, stdio: 'inherit' }); @@ -114,7 +182,17 @@ async function main() { } finally { // Ensure Storybook is terminated if (storybookProcess) { - storybookProcess.kill(); + console.log('Terminating Storybook process...'); + storybookProcess.kill('SIGTERM'); + + // Force kill if needed + setTimeout(() => { + try { + storybookProcess.kill('SIGKILL'); + } catch { + // Ignore errors if process already gone + } + }, 5000); } } } From 01475fb838d75a30abcd09fee21def762509dc79 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 22:13:57 +0200 Subject: [PATCH 05/13] fix: enhance port detection for Storybook test runner --- packages/ui-kit/scripts/run-storybook-test.js | 147 +++++++++++++++--- 1 file changed, 125 insertions(+), 22 deletions(-) diff --git a/packages/ui-kit/scripts/run-storybook-test.js b/packages/ui-kit/scripts/run-storybook-test.js index c9458f6..760d891 100755 --- a/packages/ui-kit/scripts/run-storybook-test.js +++ b/packages/ui-kit/scripts/run-storybook-test.js @@ -3,15 +3,40 @@ import { spawn, exec } from 'child_process'; import { promisify } from 'util'; +import { createServer } from 'net'; const execAsync = promisify(exec); // Script automatically detects the port Storybook is running on // Configuration -const STORYBOOK_TIMEOUT_MS = 120000; // 2 minutes +const STORYBOOK_TIMEOUT_MS = 180000; // 3 minutes const TEST_TIMEOUT_MS = 120000; // 2 minutes -const READY_WAIT_MS = 10000; // Additional wait time after port detection +const READY_WAIT_MS = 15000; // Additional wait time after port detection + +// Find an available port +async function findAvailablePort(startPort = 60000, endPort = 65000) { + let port = Math.floor(Math.random() * (endPort - startPort)) + startPort; + + return new Promise((resolve) => { + const server = createServer(); + + server.once('error', () => { + // Port is in use, try another one + resolve(findAvailablePort(startPort, endPort)); + }); + + server.once('listening', () => { + // Found an available port + const foundPort = server.address().port; + server.close(() => { + resolve(foundPort); + }); + }); + + server.listen(port); + }); +} /** * Start Storybook and detect the port it's running on @@ -19,47 +44,115 @@ const READY_WAIT_MS = 10000; // Additional wait time after port detection async function startStorybook() { console.log('Starting Storybook...'); - // Start Storybook with --ci flag and specify port range - // Using a random port in the 60000-65000 range to avoid conflicts - const randomPort = Math.floor(Math.random() * 5000) + 60000; + // Find an available port + const availablePort = await findAvailablePort(); + console.log(`Using available port: ${availablePort}`); - const storybookProcess = spawn('storybook', ['dev', '--ci', '--port', randomPort.toString()], { + // Start Storybook with --ci flag and specify port range + const storybookProcess = spawn('storybook', ['dev', '--ci', '--port', availablePort.toString()], { shell: true, - stdio: ['pipe', 'pipe', 'pipe'] + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, FORCE_COLOR: 'true' } }); let port = null; + let portDetected = false; + let logBuffer = ''; // Set up timeout for port detection const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { - reject(new Error(`Timeout waiting for Storybook to start (${STORYBOOK_TIMEOUT_MS / 1000} seconds)`)); + // Before failing, try to detect the port from lsof + tryDetectPortFromLsof(reject); }, STORYBOOK_TIMEOUT_MS); }); + // Try to detect the port from lsof as a fallback + async function tryDetectPortFromLsof(rejectFn) { + try { + console.log('Attempting to detect Storybook port from system...'); + // Check for listening Node.js processes + const { stdout } = await execAsync('lsof -i -P -n | grep LISTEN | grep node'); + console.log('Found Node.js listening ports:'); + console.log(stdout); + + // Log the whole buffer for debugging + console.log('---- Storybook Output Log ----'); + console.log(logBuffer); + console.log('---- End Storybook Output Log ----'); + + rejectFn(new Error(`Timeout waiting for Storybook to start (${STORYBOOK_TIMEOUT_MS / 1000} seconds)`)); + } catch (err) { + rejectFn(new Error(`Timeout waiting for Storybook to start. Failed to detect from lsof: ${err.message}`)); + } + } + // Create a promise that resolves when we detect the port const portDetectionPromise = new Promise((resolve) => { + // Function to check output against all known Storybook port patterns + const checkForPort = (output) => { + // Add to cumulative log buffer for debugging if detection fails + logBuffer += output; + + // Various patterns Storybook might use to report its port + const patterns = [ + // Normal patterns + /Local:\s*http:\/\/localhost:(\d+)/, + /http:\/\/127\.0\.0\.1:(\d+)/, + /http:\/\/0\.0\.0\.0:(\d+)/, + // CLI output format + /╭.*╮.*Local:\s*http:\/\/localhost:(\d+)/s, + /╭.*╮.*On your network:\s*http:\/\/[^:]+:(\d+)/s, + // Alternative pattern seen in some CI environments + /Storybook.*started.*localhost:(\d+)/i, + // Direct port mentions + /port\s+(\d+)/i, + /PORT=(\d+)/i + ]; + + for (const pattern of patterns) { + const match = output.match(pattern); + if (match && match[1]) { + const detectedPort = parseInt(match[1], 10); + if (detectedPort > 0) { + console.log(`Detected Storybook running on port: ${detectedPort} (matched pattern: ${pattern})`); + port = detectedPort; + portDetected = true; + resolve(port); + return true; + } + } + } + + // If the requested port is available and no port detection yet + if (output.includes(`${availablePort}`) && !portDetected) { + console.log(`Detected Storybook likely running on requested port: ${availablePort}`); + port = availablePort; + portDetected = true; + resolve(port); + return true; + } + + return false; + }; + storybookProcess.stdout.on('data', (data) => { const output = data.toString(); console.log(`Storybook: ${output}`); - // Look for the port in the output - try both patterns that Storybook might use - const portMatch = output.match(/Local:\s*http:\/\/localhost:(\d+)/); - const altPortMatch = output.match(/http:\/\/127.0.0.1:(\d+)/); - - if (portMatch && portMatch[1]) { - port = parseInt(portMatch[1], 10); - console.log(`Detected Storybook running on port: ${port}`); - resolve(port); - } else if (altPortMatch && altPortMatch[1]) { - port = parseInt(altPortMatch[1], 10); - console.log(`Detected Storybook running on port: ${port}`); - resolve(port); + if (!portDetected) { + checkForPort(output); } }); storybookProcess.stderr.on('data', (data) => { - console.error(`Storybook stderr: ${data}`); + const output = data.toString(); + console.error(`Storybook stderr: ${output}`); + + // Also check stderr for port information + if (!portDetected) { + checkForPort(output); + } }); storybookProcess.on('error', (error) => { @@ -71,6 +164,16 @@ async function startStorybook() { console.error(`Storybook process exited with code ${code}`); } }); + + // As a fallback, assume the port we provided worked if Storybook doesn't exit + setTimeout(() => { + if (!portDetected) { + console.log(`No port detected after 30s. Assuming port ${availablePort} is being used...`); + port = availablePort; + portDetected = true; + resolve(port); + } + }, 30000); }); // Wait for port detection or timeout @@ -118,7 +221,7 @@ async function runTests(port) { try { // Wait for Storybook server to be ready - await execAsync(`wait-on tcp:${port} -t ${TEST_TIMEOUT_MS}`); + await execAsync(`wait-on http://localhost:${port} -t ${TEST_TIMEOUT_MS}`); console.log('Storybook is ready. Running accessibility tests...'); // List available story IDs for debugging From e111760a1d937705ace504f16ee9e6f4a7500145 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 22:18:58 +0200 Subject: [PATCH 06/13] fix: remove unsupported --watchAll=false flag from test-storybook command --- packages/ui-kit/scripts/run-storybook-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui-kit/scripts/run-storybook-test.js b/packages/ui-kit/scripts/run-storybook-test.js index 760d891..46134a4 100755 --- a/packages/ui-kit/scripts/run-storybook-test.js +++ b/packages/ui-kit/scripts/run-storybook-test.js @@ -241,7 +241,6 @@ async function runTests(port) { const testProcess = spawn('test-storybook', [ `--url=http://localhost:${port}`, '--maxWorkers=2', - '--watchAll=false', '--ci' ], { shell: true, From 9d9aaa05dacf54e29a6f27bbb7faf46db14d6f0e Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 23:11:07 +0200 Subject: [PATCH 07/13] feat: implement task 2.5 - extend CI to run axe-core accessibility tests --- packages/ui-kit/docs/PROTECTED_FOLDERS.md | 151 ++++++++++++++++++ .../examples/A11yButton.stories.tsx | 108 +++++++++++++ .../src/components/examples/A11yButton.tsx | 89 +++++++++++ .../examples/AccessibleComponentsExample.tsx | 93 +++++++++++ .../form/A11yFormExamples.stories.tsx | 37 +++++ .../src/components/form/A11yFormExamples.tsx | 128 +++++++++++++++ packages/ui-kit/src/components/ui/README.md | 72 +++++++++ packages/ui-kit/src/components/ui/label.tsx | 24 +++ 8 files changed, 702 insertions(+) create mode 100644 packages/ui-kit/docs/PROTECTED_FOLDERS.md create mode 100644 packages/ui-kit/src/components/examples/A11yButton.stories.tsx create mode 100644 packages/ui-kit/src/components/examples/A11yButton.tsx create mode 100644 packages/ui-kit/src/components/examples/AccessibleComponentsExample.tsx create mode 100644 packages/ui-kit/src/components/form/A11yFormExamples.stories.tsx create mode 100644 packages/ui-kit/src/components/form/A11yFormExamples.tsx create mode 100644 packages/ui-kit/src/components/ui/README.md create mode 100644 packages/ui-kit/src/components/ui/label.tsx diff --git a/packages/ui-kit/docs/PROTECTED_FOLDERS.md b/packages/ui-kit/docs/PROTECTED_FOLDERS.md new file mode 100644 index 0000000..86aa1fb --- /dev/null +++ b/packages/ui-kit/docs/PROTECTED_FOLDERS.md @@ -0,0 +1,151 @@ +# Protected Folders Guide + +This document outlines strategies to protect certain folders in the codebase from unintentional modifications, particularly third-party components like those from shadcn. + +## Why Protect Folders? + +There are certain directories in our codebase that should rarely or never be modified directly: + +- Third-party components (e.g., `src/components/ui/` from shadcn) +- Generated code +- Core utilities that require careful consideration before changes + +Accidental modifications to these can cause: + +- Breaking changes +- Upgrade difficulties +- Unexpected regressions +- Accessibility issues + +## Methods to Protect Folders + +### 1. Git Attributes + +You can use Git attributes to mark files as "do not modify" using the `export-ignore` attribute: + +```bash +# In .gitattributes file +src/components/ui/* export-ignore +``` + +This doesn't prevent changes, but signals that these files should be treated specially. + +### 2. ESLint Rules + +Create custom ESLint rules that prevent modifications to specific directories: + +```javascript +// In eslint.config.js +module.exports = { + // ... other config + rules: { + "no-restricted-paths": [ + "error", + { + zones: [ + { + target: "src/components/ui", + message: + "⚠️ This directory contains shadcn components that should not be modified directly. Create wrapper components instead.", + }, + ], + }, + ], + }, +}; +``` + +### 3. Git Hooks + +Use pre-commit hooks to prevent commits that modify protected files: + +```bash +#!/bin/sh +# In .husky/pre-commit + +# Check if any shadcn components are being modified +if git diff --cached --name-only | grep -E "^src/components/ui/"; then + echo "⛔ You're trying to modify shadcn components, which should not be changed directly." + echo "🛠️ Create wrapper components instead or use the shadcn CLI to update components." + exit 1 +fi +``` + +### 4. Documentation & Naming Conventions + +- Add README files in protected directories +- Use naming conventions that signal "do not modify" (e.g., `vendors/`, `third-party/`) +- Add comments at the top of files + +```typescript +/** + * ⚠️ DO NOT MODIFY DIRECTLY ⚠️ + * + * This is a shadcn component. If changes are needed, use the shadcn CLI: + * npx shadcn@latest add --overwrite + * + * For custom functionality, create a wrapper component instead. + */ +``` + +### 5. Continuous Integration Checks + +Add CI checks that fail if protected files are modified without proper approvals: + +```yaml +# In GitHub workflow +- name: Check for unauthorized modifications + run: | + MODIFIED_PROTECTED=$(git diff --name-only origin/main | grep -E "^src/components/ui/") + if [ -n "$MODIFIED_PROTECTED" ]; then + echo "Protected files have been modified:" + echo "$MODIFIED_PROTECTED" + exit 1 + fi +``` + +## Best Practices for Working with Protected Components + +1. **Use Composition**: Create wrapper components that add functionality rather than modifying base components. + +2. **Use the CLI for Updates**: For shadcn components, always use the CLI to update: + + ```bash + npx shadcn@latest add --overwrite + ``` + +3. **Document Extensions**: When extending functionality, document why and how you're extending it. + +4. **Accessibility**: Ensure all extensions maintain or improve accessibility. + +## Example: Extending vs. Modifying + +### ❌ Don't modify directly: + +```tsx +// Modifying shadcn's checkbox.tsx directly +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { "aria-label"?: string } +>((props, ref) => (/* ... */)); +``` + +### ✅ Create wrapper components or use with proper patterns: + +```tsx +// In your own component +
+ + +
+``` + +## Update Process for Protected Components + +If you absolutely need to update protected components, follow these steps: + +1. Create an issue describing why the update is necessary +2. Get approval from the tech lead +3. Make the changes following the approved process (e.g., using the shadcn CLI) +4. Document the changes thoroughly +5. Add tests to verify functionality hasn't been broken diff --git a/packages/ui-kit/src/components/examples/A11yButton.stories.tsx b/packages/ui-kit/src/components/examples/A11yButton.stories.tsx new file mode 100644 index 0000000..1597476 --- /dev/null +++ b/packages/ui-kit/src/components/examples/A11yButton.stories.tsx @@ -0,0 +1,108 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { A11yButton } from './A11yButton'; + +const meta = { + title: 'Examples/A11yButton', + component: A11yButton, + parameters: { + layout: 'centered', + a11y: { + config: { + rules: [ + { id: 'button-name', enabled: true }, + { id: 'aria-valid-attr-value', enabled: true }, + ], + }, + }, + }, + argTypes: { + startIcon: { control: false }, + endIcon: { control: false }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** + * Default button with text + */ +export const Default: Story = { + args: { + children: 'Click Me', + variant: 'default', + }, +}; + +/** + * Icon-only button with aria-label + */ +export const IconOnly: Story = { + args: { + ariaLabel: 'Close dialog', + children: '×', + variant: 'outline', + }, +}; + +/** + * Button with start icon and accessible name + */ +export const WithStartIcon: Story = { + args: { + children: 'Download', + startIcon: ( + + + + ), + variant: 'default', + }, +}; + +/** + * Toggle button with aria-pressed state + */ +export const ToggleButton: Story = { + args: { + children: 'Mute Audio', + isToggle: true, + isPressed: true, + ariaLabel: 'Mute audio', + }, +}; + +/** + * Dropdown button with aria-expanded + */ +export const DropdownButton: Story = { + args: { + children: 'Menu', + ariaExpanded: false, + ariaControls: 'dropdown-menu', + endIcon: ( + + + + ), + }, +}; + +/** + * Button with description + */ +export const WithDescription: Story = { + render: () => ( +
+

+ This action permanently removes the item and cannot be undone. +

+ + Delete Item + +
+ ), +}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/examples/A11yButton.tsx b/packages/ui-kit/src/components/examples/A11yButton.tsx new file mode 100644 index 0000000..93929c9 --- /dev/null +++ b/packages/ui-kit/src/components/examples/A11yButton.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Button, ButtonProps } from '../ui/button'; +import { cn } from '@/lib/utils'; + +type A11yButtonProps = ButtonProps & { + /** + * Aria label for better accessibility - use this when button only contains icons + * or when the button text is not descriptive enough + */ + ariaLabel?: string; + + /** + * ID of an element that describes this button for accessibility + */ + ariaDescribedBy?: string; + + /** + * ID of an element that labels this button for accessibility + */ + ariaLabelledBy?: string; + + /** + * Button is expanded (for use with dropdowns, modals, etc.) + */ + ariaExpanded?: boolean; + + /** + * Controls element ID (for use with dropdowns, modals, etc.) + */ + ariaControls?: string; + + /** + * Optional icon to display before the button text + */ + startIcon?: React.ReactNode; + + /** + * Optional icon to display after the button text + */ + endIcon?: React.ReactNode; + + /** + * Whether the button is being used as a toggle + */ + isToggle?: boolean; + + /** + * Whether the button is in "pressed" state (for toggles) + */ + isPressed?: boolean; +}; + +/** + * A11yButton is a wrapper around the core Button component + * that adds additional accessibility features without modifying + * the core component. + */ +export function A11yButton({ + children, + className, + ariaLabel, + ariaDescribedBy, + ariaLabelledBy, + ariaExpanded, + ariaControls, + startIcon, + endIcon, + isToggle, + isPressed, + ...props +}: A11yButtonProps) { + return ( + + ); +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/examples/AccessibleComponentsExample.tsx b/packages/ui-kit/src/components/examples/AccessibleComponentsExample.tsx new file mode 100644 index 0000000..42942db --- /dev/null +++ b/packages/ui-kit/src/components/examples/AccessibleComponentsExample.tsx @@ -0,0 +1,93 @@ +import { Checkbox } from '../ui/checkbox'; +import { Label } from '../ui/label'; +import { RadioGroup, RadioGroupItem } from '../ui/radio-group'; +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../ui/select'; + +/** + * AccessibleComponentsExample demonstrates how to use shadcn components with proper + * accessibility attributes without modifying the base components. + * + * Key accessibility patterns: + * 1. Associate labels with form controls using htmlFor/id + * 2. Group related form elements + * 3. Provide descriptive text for controls + * 4. Use semantic HTML elements + */ +export function AccessibleComponentsExample() { + return ( +
+

Accessible Component Examples

+ + {/* Checkbox with associated label */} +
+

Checkbox

+
+ + +
+

+ The key to accessible checkboxes is using a proper id attribute and + associating it with a Label using htmlFor. +

+
+ + {/* Radio group with proper labeling */} +
+

Radio Group

+

Select your preferred contact method:

+ +
+ + +
+
+ + +
+
+ + +
+
+

+ For radio groups, each radio item needs its own unique id and + associated Label. +

+
+ + {/* Select with proper labeling */} +
+

Select

+
+ + +
+

+ For select components, the SelectTrigger needs an id + that matches the htmlFor of the Label. +

+
+ +
+

Accessibility Best Practices:

+
    +
  • Always associate labels with form controls using proper IDs
  • +
  • Use semantic HTML elements when possible
  • +
  • Ensure keyboard navigation works for all interactive elements
  • +
  • Provide enough color contrast for text and interactive elements
  • +
  • Use proper heading hierarchy (h1, h2, h3, etc.)
  • +
  • Add descriptive text for complex form controls
  • +
+
+
+ ); +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/form/A11yFormExamples.stories.tsx b/packages/ui-kit/src/components/form/A11yFormExamples.stories.tsx new file mode 100644 index 0000000..4b10021 --- /dev/null +++ b/packages/ui-kit/src/components/form/A11yFormExamples.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { A11yFormExamples } from './A11yFormExamples'; + +const meta = { + title: 'Form/AccessibleExamples', + component: A11yFormExamples, + parameters: { + layout: 'centered', + // Run thorough accessibility tests on this component + a11y: { + config: { + rules: [ + // Ensure these critical rules are enabled + { id: 'button-name', enabled: true }, + { id: 'label', enabled: true }, + { id: 'aria-valid-attr-value', enabled: true }, + { id: 'color-contrast', enabled: true }, + ], + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** + * This story demonstrates a form built with shadcn components + * and proper accessibility practices. + * + * Key a11y features: + * - All form controls have associated labels + * - Descriptions use aria-describedby + * - Proper semantic structure with fieldset/legend + * - Explicit IDs for all interactive elements + */ +export const Default: Story = {}; \ No newline at end of file diff --git a/packages/ui-kit/src/components/form/A11yFormExamples.tsx b/packages/ui-kit/src/components/form/A11yFormExamples.tsx new file mode 100644 index 0000000..e812534 --- /dev/null +++ b/packages/ui-kit/src/components/form/A11yFormExamples.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Button } from '../ui/button'; +import { Checkbox } from '../ui/checkbox'; +import { Label } from '../ui/label'; +import { RadioGroup, RadioGroupItem } from '../ui/radio-group'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; +import { Input } from '../ui/input'; + +/** + * This file demonstrates how to use shadcn components with proper accessibility + * while NOT modifying the base components themselves. + * + * Key principles: + * 1. Use unique IDs for form elements + * 2. Associate labels with form controls using htmlFor/id + * 3. Use aria-describedby for descriptions + * 4. Group related form elements with fieldset and legend when appropriate + */ + +export function A11yFormExamples() { + const [formValues, setFormValues] = React.useState({ + name: '', + acceptTerms: false, + contactMethod: 'email', + role: '' + }); + + // Unique ID generation for form controls - in a real app use useId() from React + const ids = { + name: 'user-name', + terms: 'accept-terms', + termsDescription: 'terms-description', + contactMethod: 'contact-method', + contactEmail: 'contact-email', + contactPhone: 'contact-phone', + contactMail: 'contact-mail', + role: 'user-role', + roleDescription: 'role-description', + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + console.log('Form submitted:', formValues); + }; + + return ( +
+

Accessible Form Example

+ + {/* Input with associated label */} +
+ + setFormValues({ ...formValues, name: e.target.value })} + placeholder="Enter your name" + /> +
+ + {/* Checkbox with associated label and description */} +
+
+ setFormValues({ ...formValues, acceptTerms: checked as boolean })} + aria-describedby={ids.termsDescription} + /> + +
+

+ By checking this box, you agree to our Terms of Service and Privacy Policy. +

+
+ + {/* Radio group with fieldset and legend for proper grouping */} +
+ Preferred contact method + setFormValues({ ...formValues, contactMethod: value })} + className="flex flex-col space-y-1" + aria-labelledby={ids.contactMethod} + > +
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Select with associated label and description */} +
+ + +

+ This determines your permissions within the system. +

+
+ + {/* Button with descriptive text */} + +
+ ); +} \ No newline at end of file diff --git a/packages/ui-kit/src/components/ui/README.md b/packages/ui-kit/src/components/ui/README.md new file mode 100644 index 0000000..dbda242 --- /dev/null +++ b/packages/ui-kit/src/components/ui/README.md @@ -0,0 +1,72 @@ +# ⚠️ DO NOT MODIFY COMPONENTS IN THIS DIRECTORY ⚠️ + +This directory contains components from [shadcn/ui](https://ui.shadcn.com/), which are meant to be used as-is and not modified directly. + +## Why not modify these components? + +1. **Updates are blocked**: Modifying these files makes it difficult to update components when new versions are released. +2. **Accessibility regressions**: These components have been designed with accessibility in mind - modifications may break a11y. +3. **Maintenance overhead**: Custom changes create maintenance burden and technical debt. +4. **Integration issues**: Modifications may cause issues with other shadcn components. + +## How to update these components + +Always use the shadcn CLI to add or update components: + +```bash +# Update or reinstall a component +npx shadcn@latest add checkbox --overwrite --yes +``` + +## How to extend these components + +Instead of modifying the base components, use these recommended patterns: + +### 1. Composition Pattern + +Create new components that use the shadcn components and add your functionality: + +```tsx +// ✅ DO THIS: Create a wrapper component +export function AccessibleCheckbox({ label, id, ...props }) { + return ( +
+ + +
+ ); +} +``` + +### 2. Proper Label Association + +Always use labels properly with form components: + +```tsx +// ✅ DO THIS: Properly associate labels +
+ + +
+``` + +### 3. Use Description Elements with aria-describedby + +For form descriptions and help text: + +```tsx +// ✅ DO THIS: Use proper element association +
+ + +

We'll never share your email.

+
+``` + +## Questions & Issues + +If you're encountering an issue with these components, or need help extending them properly, please: + +1. Check the [shadcn documentation](https://ui.shadcn.com/docs) +2. See the examples in `src/components/examples` directory for proper usage patterns +3. Consult the accessibility guidelines in `docs/ACCESSIBILITY.md` diff --git a/packages/ui-kit/src/components/ui/label.tsx b/packages/ui-kit/src/components/ui/label.tsx new file mode 100644 index 0000000..683faa7 --- /dev/null +++ b/packages/ui-kit/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } From 5ed186829deb7e3b43023bc9b11a126720bd749a Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 23:13:41 +0200 Subject: [PATCH 08/13] docs: update task 2.5 planning document with final status --- docs/task-planning/task-2.5-axe-core-ci.md | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/task-planning/task-2.5-axe-core-ci.md b/docs/task-planning/task-2.5-axe-core-ci.md index 6ad91e5..4d4cba8 100644 --- a/docs/task-planning/task-2.5-axe-core-ci.md +++ b/docs/task-planning/task-2.5-axe-core-ci.md @@ -25,7 +25,7 @@ Task 2.5 involves extending the CI pipeline to run axe-core accessibility tests ## Implementation Notes - We've implemented the axe-core accessibility testing in CI using the Storybook test runner -- A test component with intentional accessibility violations was created (`A11yTestButton`) +- A test component with intentional accessibility violations was created (`A11yTestButton` and `A11yViolatingForm`) - We created a standalone test script (`scripts/test-a11y-violation.js`) to verify axe-core detection - Manual verification confirms axe-core successfully detects accessibility violations: ``` @@ -39,6 +39,30 @@ Task 2.5 involves extending the CI pipeline to run axe-core accessibility tests - The CI pipeline will fail when new accessibility violations are introduced, meeting the Definition of Done - Documentation explaining how to interpret and fix a11y issues has been added to the `packages/ui-kit/docs/accessibility-testing.md` file +## Key Accessibility Improvements + +1. Created proper patterns for extending shadcn components without modification: + + - Composition pattern (`A11yButton`) + - Associate labels with form controls (`A11yFormExamples`) + - Use aria attributes correctly (`AccessibleComponentsExample`) + +2. Added comprehensive documentation: + - `packages/ui-kit/docs/PROTECTED_FOLDERS.md` - Guidelines for not modifying third-party components + - `packages/ui-kit/src/components/ui/README.md` - Instructions for extending shadcn components + ## Expected Outcome After implementation, the CI pipeline will automatically test all Storybook stories for accessibility issues using axe-core. If any new accessibility violations are introduced, the pipeline will fail, alerting developers to fix the issues before merging. + +## Final Status + +Pull request [#13](https://github.com/etherisc/ui-kit/pull/13) has been created and is awaiting review and merge. + +## Cleanup Notes + +After the PR is merged: + +1. Delete the `feature/axe-core-ci` branch locally and remotely +2. Delete the `test/a11y-violation-detection` branch locally and remotely (it was used for testing/development) +3. Verify a11y tests are running in CI on the develop branch From 225728d9407dbc7d729a82fb842d4172d1ecba66 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 23:15:02 +0200 Subject: [PATCH 09/13] chore: include remaining changes for task 2.5 --- .husky/pre-commit | 3 +- .husky/pre-push | 6 + packages/ui-kit/package.json | 1 + .../ui-kit/src/components/ui/checkbox.tsx | 32 ++- .../ui-kit/src/components/ui/radio-group.tsx | 52 +++-- packages/ui-kit/src/components/ui/select.tsx | 216 +++++++++--------- packages/ui-kit/src/hooks/useTheme.ts | 33 ++- packages/ui-kit/src/stories/button.css | 9 +- pnpm-lock.yaml | 63 +++++ 9 files changed, 252 insertions(+), 163 deletions(-) create mode 100755 .husky/pre-push diff --git a/.husky/pre-commit b/.husky/pre-commit index 064028c..433de1e 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,5 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -pnpm lint-staged && pnpm lint && pnpm test && pnpm build +# Only run lint-staged and lint for faster commits +pnpm lint-staged && pnpm lint diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..0e3d64c --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# Run full test and build suite before pushing +echo "Running full test and build suite before pushing..." +pnpm lint && pnpm test && pnpm build \ No newline at end of file diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index 43c48a2..dba5250 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -38,6 +38,7 @@ "dependencies": { "@hookform/resolvers": "^5.0.1", "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-radio-group": "^1.3.6", "@radix-ui/react-select": "^2.2.4", "@radix-ui/react-slot": "^1.2.2", diff --git a/packages/ui-kit/src/components/ui/checkbox.tsx b/packages/ui-kit/src/components/ui/checkbox.tsx index 26c488a..0a6a9a5 100644 --- a/packages/ui-kit/src/components/ui/checkbox.tsx +++ b/packages/ui-kit/src/components/ui/checkbox.tsx @@ -1,5 +1,3 @@ -"use client" - import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" @@ -7,23 +5,23 @@ import { Check } from "lucide-react" import { cn } from "@/lib/utils" const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + - - - - + + + )) Checkbox.displayName = CheckboxPrimitive.Root.displayName diff --git a/packages/ui-kit/src/components/ui/radio-group.tsx b/packages/ui-kit/src/components/ui/radio-group.tsx index 56f96f0..0bdf685 100644 --- a/packages/ui-kit/src/components/ui/radio-group.tsx +++ b/packages/ui-kit/src/components/ui/radio-group.tsx @@ -1,3 +1,5 @@ +"use client" + import * as React from "react" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import { Circle } from "lucide-react" @@ -5,37 +7,37 @@ import { Circle } from "lucide-react" import { cn } from "@/lib/utils" const RadioGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { - return ( - - ) + return ( + + ) }) RadioGroup.displayName = RadioGroupPrimitive.Root.displayName const RadioGroupItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { - return ( - - - - - - ) + return ( + + + + + + ) }) RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName diff --git a/packages/ui-kit/src/components/ui/select.tsx b/packages/ui-kit/src/components/ui/select.tsx index 2002f34..2ca1df8 100644 --- a/packages/ui-kit/src/components/ui/select.tsx +++ b/packages/ui-kit/src/components/ui/select.tsx @@ -11,147 +11,147 @@ const SelectGroup = SelectPrimitive.Group const SelectValue = SelectPrimitive.Value const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - span]:line-clamp-1", - className - )} - {...props} - > - {children} - - - - + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + )) SelectTrigger.displayName = SelectPrimitive.Trigger.displayName const SelectScrollUpButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - + + + )) SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName const SelectScrollDownButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - + + + )) SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName + SelectPrimitive.ScrollDownButton.displayName const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, position = "popper", ...props }, ref) => ( - - - - - {children} - - - - + + + + + {children} + + + + )) SelectContent.displayName = SelectPrimitive.Content.displayName const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) SelectLabel.displayName = SelectPrimitive.Label.displayName const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - - {children} - + + + + + + + {children} + )) SelectItem.displayName = SelectPrimitive.Item.displayName const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) SelectSeparator.displayName = SelectPrimitive.Separator.displayName export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, - SelectScrollUpButton, - SelectScrollDownButton, + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, } diff --git a/packages/ui-kit/src/hooks/useTheme.ts b/packages/ui-kit/src/hooks/useTheme.ts index 8ea7bf6..7f77db0 100644 --- a/packages/ui-kit/src/hooks/useTheme.ts +++ b/packages/ui-kit/src/hooks/useTheme.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback, useRef } from 'react'; import { useThemeState } from '../store/sessionStore'; /** @@ -10,9 +10,23 @@ import { useThemeState } from '../store/sessionStore'; export function useTheme() { const { isDarkMode, setDarkMode, toggleDarkMode } = useThemeState(); const [systemPrefersDark, setSystemPrefersDark] = useState(false); + const systemPrefInitialized = useRef(false); + const isDarkModeRef = useRef(isDarkMode); - // Detect system preference + // Update ref when isDarkMode changes + useEffect(function updateDarkModeRef() { + isDarkModeRef.current = isDarkMode; + + return function cleanupDarkModeRef() { + // No cleanup needed for a simple ref update + }; + }, [isDarkMode]); + + // Detect system preference - only run once to avoid infinite loops useEffect(function setupSystemPreference() { + if (systemPrefInitialized.current) return; + systemPrefInitialized.current = true; + // Check for system dark mode preference const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); setSystemPrefersDark(mediaQuery.matches); @@ -28,23 +42,22 @@ export function useTheme() { }; }, []); - // Helper to sync with system preference - const syncWithSystemPreference = () => { + // Helper to sync with system preference - memoize the function + const syncWithSystemPreference = useCallback(() => { setDarkMode(systemPrefersDark); - }; + }, [systemPrefersDark, setDarkMode]); // Set theme class on document useEffect(function applyThemeClass() { + const html = document.documentElement; if (isDarkMode) { - document.documentElement.classList.add('dark'); + html.classList.add('dark'); } else { - document.documentElement.classList.remove('dark'); + html.classList.remove('dark'); } - // Cleanup function to remove theme class if component unmounts return function cleanupThemeClass() { - // This is a no-op in this case, but we include it to comply with our ESLint rule - // In a real-world scenario, you might want to restore the previous theme + // No cleanup needed for class modifications }; }, [isDarkMode]); diff --git a/packages/ui-kit/src/stories/button.css b/packages/ui-kit/src/stories/button.css index dc91dc7..96f75bf 100644 --- a/packages/ui-kit/src/stories/button.css +++ b/packages/ui-kit/src/stories/button.css @@ -7,24 +7,29 @@ display: inline-block; line-height: 1; } + .storybook-button--primary { color: white; - background-color: #1ea7fd; + background-color: #0b76c9; } + .storybook-button--secondary { color: #333; background-color: transparent; box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; } + .storybook-button--small { font-size: 12px; padding: 10px 16px; } + .storybook-button--medium { font-size: 14px; padding: 11px 20px; } + .storybook-button--large { font-size: 16px; padding: 12px 24px; -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1fc32f..f2b7e62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: '@radix-ui/react-checkbox': specifier: ^1.3.1 version: 1.3.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-label': + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-radio-group': specifier: ^1.3.6 version: 1.3.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1731,6 +1734,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.6': resolution: {integrity: sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg==} peerDependencies: @@ -1783,6 +1799,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-radio-group@1.3.6': resolution: {integrity: sha512-1tfTAqnYZNVwSpFhCT273nzK8qGBReeYnNTPspCggqk1fvIrfVxJekIuBFidNivzpdiMqDwVGnQvHqXrRPM4Og==} peerDependencies: @@ -1831,6 +1860,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: @@ -8135,6 +8173,15 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-popper@1.2.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -8182,6 +8229,15 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.4 + '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-radio-group@1.3.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -8253,6 +8309,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-slot@1.2.3(@types/react@19.1.4)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.4 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 From e3ffba0285148886f1bc943e413d2a9ec9ee4177 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Tue, 20 May 2025 23:27:55 +0200 Subject: [PATCH 10/13] fix: improve a11y testing infrastructure and fix form accessibility issues --- .husky/pre-push | 2 +- package.json | 1 + .../src/components/FormExample.stories.tsx | 29 ++++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.husky/pre-push b/.husky/pre-push index 0e3d64c..c1984c9 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -3,4 +3,4 @@ # Run full test and build suite before pushing echo "Running full test and build suite before pushing..." -pnpm lint && pnpm test && pnpm build \ No newline at end of file +pnpm lint && pnpm test && pnpm test:a11y && pnpm build \ No newline at end of file diff --git a/package.json b/package.json index 4c1a771..ceee3d8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build": "pnpm -r build", "dev": "pnpm --filter @org/ui-kit dev", "test": "pnpm -r test", + "test:a11y": "cd packages/ui-kit && pnpm run test-storybook:ci", "lint": "pnpm -r lint", "format": "prettier --write \"**/*.{ts,tsx,md}\"", "prepare": "husky install" diff --git a/packages/ui-kit/src/components/FormExample.stories.tsx b/packages/ui-kit/src/components/FormExample.stories.tsx index 521dbe9..84257c8 100644 --- a/packages/ui-kit/src/components/FormExample.stories.tsx +++ b/packages/ui-kit/src/components/FormExample.stories.tsx @@ -29,6 +29,14 @@ const FormExample = () => { newsletter: false, }); + // Generate unique IDs for form elements + const ids = { + firstName: React.useId(), + age: React.useId(), + color: React.useId(), + newsletter: React.useId(), + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); alert(JSON.stringify(formState, null, 2)); @@ -42,6 +50,7 @@ const FormExample = () => {
{ /> { description="Must be between 0 and 120" /> - handleChange('color', value)} + aria-labelledby={ids.color} + /> +
{ /> handleChange('newsletter', !!checked)} From 7f5b84f400bffd9b56e20af1ccee711b42ce5648 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Wed, 21 May 2025 13:40:45 +0200 Subject: [PATCH 11/13] fix: accessibility issues in Checkbox, Select, RadioGroup, and ThemeToggle components --- docs/task-planning/task-2.5-a11y-fixes.md | 103 +++++++ .../ui-kit/scripts/test-single-component.js | 257 ++++++++++++++++++ .../primitives/RadioGroup/RadioGroup.tsx | 78 ++++-- .../ThemeToggle/ThemeToggle.stories.tsx | 37 ++- .../primitives/ThemeToggle/ThemeToggle.tsx | 12 +- packages/ui-kit/src/hooks/useTheme.mock.ts | 29 ++ 6 files changed, 486 insertions(+), 30 deletions(-) create mode 100644 docs/task-planning/task-2.5-a11y-fixes.md create mode 100755 packages/ui-kit/scripts/test-single-component.js create mode 100644 packages/ui-kit/src/hooks/useTheme.mock.ts diff --git a/docs/task-planning/task-2.5-a11y-fixes.md b/docs/task-planning/task-2.5-a11y-fixes.md new file mode 100644 index 0000000..4154ee0 --- /dev/null +++ b/docs/task-planning/task-2.5-a11y-fixes.md @@ -0,0 +1,103 @@ +# Task 2.5 - Accessibility Fixes + +## Overview + +Based on the accessibility test results, we need to address several accessibility issues in our components to make the UI-Kit fully accessible. The goal is to ensure that all components pass the accessibility tests and the CI pipeline can run without errors. + +## Issues Identified + +From the test results, we have identified the following main accessibility issues: + +1. **Checkbox Component**: All checkbox variants have `button-name` violations - Critical + + - Issue: Buttons (checkbox role) do not have discernible text + - Components affected: All checkbox variants + - Status: FIXED ✅ - Added proper aria-label attributes and ID associations + +2. **Select Component**: Select trigger has `button-name` violations - Critical + + - Issue: Buttons (combobox role) do not have discernible text + - Components affected: All select variants + - Status: FIXED ✅ - Added proper aria-label attributes and ID associations + +3. **RadioGroup Component**: Radio buttons have `button-name` violations - Critical + + - Issue: Buttons (radio role) do not have discernible text + - Components affected: All radio group variants + - Status: FIXED ✅ - Added proper aria-label attributes and ID associations + +4. **ThemeToggle Component**: React maximum update depth exceeded errors + + - Issue: Infinite rendering loop in the ThemeToggle stories + - Components affected: ThemeToggle and ThemeProvider stories + - Status: FIXED ✅ - Created mock hook for testing and dependency injection + +5. **Example Pages**: Color contrast issues - Serious + - Issue: Insufficient contrast between foreground and background colors + - Components affected: Example/Page stories + +## Tasks + +| Task Description | DoD (Definition of Done) | Status | +| ------------------------------------------ | ------------------------------------------------------------------------------ | ----------- | +| Fix Checkbox button-name violations | Checkbox component passes accessibility tests with no button-name violations | Complete ✅ | +| Fix Select button-name violations | Select component passes accessibility tests with no button-name violations | Complete ✅ | +| Fix RadioGroup button-name violations | RadioGroup component passes accessibility tests with no button-name violations | Complete ✅ | +| Fix ThemeToggle infinite update loop | ThemeToggle stories render without errors | Complete ✅ | +| Fix color contrast issues in example pages | Example pages pass contrast testing | Open | +| Verify all fixes with test-storybook:ci | All tests pass with no accessibility violations | Open | + +## Implementation Plan + +### 1. ✅ Fix Checkbox Component + +The checkbox component needed proper accessible names. This was achieved by: + +- Adding appropriate aria-label attributes for checkboxes without visible text labels +- Ensuring ID association between checkboxes and their labels using React.useId() +- Properly associating description and error text with the checkboxes + +### 2. ✅ Fix Select Component + +The Select component needed proper accessible names. This was achieved by: + +- Adding appropriate aria-label attributes for the select trigger when no label is present +- Ensuring ID association between select triggers and their labels using React.useId() +- Properly connecting description and error messages using aria-describedby + +### 3. ✅ Fix RadioGroup Component + +The RadioGroup component needed proper accessible names. This was achieved by: + +- Adding unique IDs for each radio button option using generatedId + option value +- Creating proper label associations using htmlFor/id pairs +- Adding aria-label to each RadioGroupItem for screen readers +- Properly connecting the group label, description and error messages using aria-labelledby and aria-describedby + +### 4. ✅ Fix ThemeToggle Component + +The ThemeToggle component's stories had infinite update loops that were addressed by: + +- Creating a mock useTheme hook (useThemeMock) with stable state management +- Making the ThemeToggle component accept an optional useThemeHook prop for dependency injection +- Updating stories to use the mock hook instead of the real one +- Breaking circular dependencies between story rendering and theme state updates + +### 5. Fix Color Contrast Issues + +For the example page components with contrast issues: + +- Update the color palette to meet WCAG 2.1 AA contrast requirements (minimum 4.5:1 for normal text) +- Replace problematic color combinations +- Exclude example components from test if they are intentionally showing poor practices + +## Testing Methodology + +We've created a script to test individual components for accessibility: + +- `pnpm test:component "ComponentName"` - Tests a specific component for accessibility violations +- This script starts Storybook if not already running and runs axe-core tests on the specified stories + +## Conclusion + +We've made excellent progress fixing the accessibility issues, with the Checkbox, Select, RadioGroup, and ThemeToggle components now passing all tests. We'll continue addressing the remaining components to ensure the entire UI-Kit meets accessibility standards. diff --git a/packages/ui-kit/scripts/test-single-component.js b/packages/ui-kit/scripts/test-single-component.js new file mode 100755 index 0000000..7233be2 --- /dev/null +++ b/packages/ui-kit/scripts/test-single-component.js @@ -0,0 +1,257 @@ +#!/usr/bin/env node +/* eslint-disable no-console, no-undef */ + +import { spawn, exec } from 'child_process'; +import { promisify } from 'util'; +import { createServer } from 'net'; +import { setTimeout as sleep } from 'timers/promises'; + +const execAsync = promisify(exec); + +// Get the story pattern from command line arguments +const storyPattern = process.argv[2]; +if (!storyPattern) { + console.error('Please provide a story pattern to test!'); + console.error('Usage: node test-single-component.js "Components/Form/Checkbox*"'); + process.exit(1); +} + +// Configuration +const STORYBOOK_TIMEOUT_MS = 60000; // 1 minute to start Storybook +const TEST_TIMEOUT_MS = 30000; // 30 seconds for tests +const READY_WAIT_MS = 5000; // Additional wait after port detection + +/** + * Find an available port + */ +async function findAvailablePort(startPort = 60000, endPort = 65000) { + let port = Math.floor(Math.random() * (endPort - startPort)) + startPort; + + return new Promise((resolve) => { + const server = createServer(); + + server.once('error', () => { + // Port is in use, try another one + resolve(findAvailablePort(startPort, endPort)); + }); + + server.once('listening', () => { + // Found an available port + const foundPort = server.address().port; + server.close(() => { + resolve(foundPort); + }); + }); + + server.listen(port); + }); +} + +/** + * Start Storybook and detect the port it's running on + */ +async function startStorybook() { + console.log('Starting Storybook...'); + + // Find an available port + const availablePort = await findAvailablePort(); + console.log(`Using available port: ${availablePort}`); + + // Start Storybook with --ci flag and specify port + const storybookProcess = spawn('storybook', ['dev', '--ci', '--port', availablePort.toString()], { + shell: true, + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, FORCE_COLOR: 'true' } + }); + + let port = null; + let portDetected = false; + + // Set up timeout for port detection + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(`Timeout waiting for Storybook to start (${STORYBOOK_TIMEOUT_MS / 1000} seconds)`)); + }, STORYBOOK_TIMEOUT_MS); + }); + + // Create a promise that resolves when we detect the port + const portDetectionPromise = new Promise((resolve) => { + // Function to check output against all known Storybook port patterns + const checkForPort = (output) => { + // Various patterns Storybook might use to report its port + const patterns = [ + // Normal patterns + /Local:\s*http:\/\/localhost:(\d+)/, + /http:\/\/127\.0\.0\.1:(\d+)/, + /http:\/\/0\.0\.0\.0:(\d+)/, + // CLI output format + /╭.*╮.*Local:\s*http:\/\/localhost:(\d+)/s, + /╭.*╮.*On your network:\s*http:\/\/[^:]+:(\d+)/s, + // Alternative pattern seen in some CI environments + /Storybook.*started.*localhost:(\d+)/i, + // Direct port mentions + /port\s+(\d+)/i, + /PORT=(\d+)/i + ]; + + for (const pattern of patterns) { + const match = output.match(pattern); + if (match && match[1]) { + const detectedPort = parseInt(match[1], 10); + if (detectedPort > 0) { + console.log(`Detected Storybook running on port: ${detectedPort} (matched pattern: ${pattern})`); + port = detectedPort; + portDetected = true; + resolve(port); + return true; + } + } + } + + // If the requested port is available and no port detection yet + if (output.includes(`${availablePort}`) && !portDetected) { + console.log(`Detected Storybook likely running on requested port: ${availablePort}`); + port = availablePort; + portDetected = true; + resolve(port); + return true; + } + + return false; + }; + + storybookProcess.stdout.on('data', (data) => { + const output = data.toString(); + console.log(`Storybook: ${output}`); + + if (!portDetected) { + checkForPort(output); + } + }); + + storybookProcess.stderr.on('data', (data) => { + const output = data.toString(); + console.error(`Storybook stderr: ${output}`); + + // Also check stderr for port information + if (!portDetected) { + checkForPort(output); + } + }); + + storybookProcess.on('error', (error) => { + console.error(`Storybook process error: ${error.message}`); + }); + + storybookProcess.on('exit', (code) => { + if (code !== null && code !== 0) { + console.error(`Storybook process exited with code ${code}`); + } + }); + + // As a fallback, assume the port we provided worked if Storybook doesn't exit + setTimeout(() => { + if (!portDetected) { + console.log(`No port detected after 30s. Assuming port ${availablePort} is being used...`); + port = availablePort; + portDetected = true; + resolve(port); + } + }, 30000); + }); + + // Wait for port detection or timeout + try { + port = await Promise.race([portDetectionPromise, timeoutPromise]); + } catch (error) { + console.error('Error detecting Storybook port:', error); + process.exit(1); + } + + // Give extra time for Storybook to fully initialize + console.log(`Waiting additional ${READY_WAIT_MS / 1000} seconds for Storybook to fully initialize...`); + await sleep(READY_WAIT_MS); + + return { storybookProcess, port }; +} + +/** + * Run the test-storybook command with the detected port + */ +async function runTests(port, storyPattern) { + console.log(`Running tests on Storybook at port ${port} for stories matching: ${storyPattern}...`); + + try { + // Wait for Storybook server to be ready + await execAsync(`wait-on http://localhost:${port} -t ${TEST_TIMEOUT_MS}`); + console.log('Storybook is ready. Running accessibility tests...'); + + // Run the tests for the specific story pattern + const testCommand = `test-storybook --url=http://localhost:${port} --stories="${storyPattern}" --maxWorkers=1 --ci`; + console.log(`Running command: ${testCommand}`); + + const testProcess = spawn(testCommand, [], { + shell: true, + stdio: 'inherit' + }); + + return new Promise((resolve, reject) => { + testProcess.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Accessibility tests failed with exit code ${code}`)); + } + }); + }); + } catch (error) { + console.error('Error running tests:', error.message); + throw error; + } +} + +async function main() { + let storybookProcess = null; + + try { + // Start Storybook + const result = await startStorybook(); + storybookProcess = result.storybookProcess; + + // Run the tests + await runTests(result.port, storyPattern); + + console.log('\n✅ Accessibility tests completed successfully'); + } catch (error) { + console.error(`\n❌ Error: ${error.message}`); + process.exitCode = 1; + } finally { + // Clean up + if (storybookProcess) { + console.log('Stopping Storybook...'); + // On Unix-like systems, use SIGTERM for clean shutdown + storybookProcess.kill('SIGTERM'); + + // Give it a moment to shut down gracefully + await sleep(1000); + + // If still running, force kill + try { + process.kill(storybookProcess.pid, 0); // Check if process is still alive + console.log('Storybook still running, force killing...'); + storybookProcess.kill('SIGKILL'); + } catch { + // Process already dead, good! + // No need to use a parameter here + } + } + + console.log('Done!'); + process.exit(process.exitCode); + } +} + +main().catch(error => { + console.error('Unhandled error:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx index df578df..39ad23f 100644 --- a/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx +++ b/packages/ui-kit/src/components/primitives/RadioGroup/RadioGroup.tsx @@ -19,48 +19,84 @@ export interface RadioGroupProps extends Omit( ( - { label, description, error, className, options, disabled, ...props }, + { label, description, error, className, options, disabled, id, ...props }, ref ) => { + // Generate a unique ID if none is provided + const generatedId = React.useId(); + // Use provided id or fall back to generated one + const groupId = id || generatedId; + + // Create IDs for associated elements + const descriptionId = description ? `${groupId}-description` : undefined; + const errorId = error ? `${groupId}-error` : undefined; + + // Combine aria-describedby values + const ariaDescribedBy = [descriptionId, errorId].filter(Boolean).join(' ') || undefined; + return (
{label && ( -
); diff --git a/packages/ui-kit/src/components/primitives/ThemeToggle/ThemeToggle.stories.tsx b/packages/ui-kit/src/components/primitives/ThemeToggle/ThemeToggle.stories.tsx index eeda552..ce2e1b0 100644 --- a/packages/ui-kit/src/components/primitives/ThemeToggle/ThemeToggle.stories.tsx +++ b/packages/ui-kit/src/components/primitives/ThemeToggle/ThemeToggle.stories.tsx @@ -1,6 +1,9 @@ +import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { ThemeToggle } from './ThemeToggle'; +import { useThemeMock } from '../../../hooks/useTheme.mock'; +// Simplify the stories by using our mock hook const meta: Meta = { title: 'Components/Primitives/ThemeToggle', component: ThemeToggle, @@ -27,36 +30,54 @@ const meta: Meta = { control: 'text', description: 'Additional CSS classes', }, + // Hide the internal prop from the docs + useThemeHook: { + table: { + disable: true, + }, + }, }, }; export default meta; type Story = StoryObj; +// Define common props to avoid repetition +const commonProps = { + useThemeHook: useThemeMock +}; + export const Default: Story = { - args: {}, + args: { + ...commonProps + } }; export const Small: Story = { args: { - size: 'sm', - }, + ...commonProps, + size: 'sm' + } }; export const Large: Story = { args: { - size: 'lg', - }, + ...commonProps, + size: 'lg' + } }; export const CustomStyle: Story = { args: { - className: 'bg-secondary text-secondary-content', - }, + ...commonProps, + className: 'bg-secondary text-secondary-content' + } }; export const WithCallback: Story = { - args: {}, + args: { + ...commonProps + }, render: (args) => ( ; + export interface ThemeToggleProps { /** * Additional class names to apply to the toggle @@ -17,6 +20,12 @@ export interface ThemeToggleProps { * @default 'md' */ size?: 'sm' | 'md' | 'lg'; + + /** + * Hook to use for theme state (for testing purposes) + * @internal + */ + useThemeHook?: () => UseThemeHook; } /** @@ -26,8 +35,9 @@ export function ThemeToggle({ className, onToggle, size = 'md', + useThemeHook = useTheme, }: ThemeToggleProps) { - const { isDarkMode, toggleDarkMode } = useTheme(); + const { isDarkMode, toggleDarkMode } = useThemeHook(); const handleToggle = () => { toggleDarkMode(); diff --git a/packages/ui-kit/src/hooks/useTheme.mock.ts b/packages/ui-kit/src/hooks/useTheme.mock.ts new file mode 100644 index 0000000..a66cd55 --- /dev/null +++ b/packages/ui-kit/src/hooks/useTheme.mock.ts @@ -0,0 +1,29 @@ +import { useState, useCallback } from 'react'; + +/** + * A mock implementation of the useTheme hook for testing purposes + * This avoids the infinite update issues in the ThemeToggle component's stories + */ +export function useThemeMock() { + const [isDarkMode, setDarkMode] = useState(false); + + const toggleDarkMode = useCallback(() => { + setDarkMode(prevMode => !prevMode); + }, []); + + // Mock the system preference hooks + const [systemPrefersDark] = useState(false); + + // Mock the sync method + const syncWithSystemPreference = useCallback(() => { + setDarkMode(systemPrefersDark); + }, [systemPrefersDark]); + + return { + isDarkMode, + setDarkMode, + toggleDarkMode, + systemPrefersDark, + syncWithSystemPreference, + }; +} \ No newline at end of file From efd2553e45dbe3bd192fd23eee8a4e1b9bced232 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Wed, 21 May 2025 16:39:10 +0200 Subject: [PATCH 12/13] fix: complete a11y fixes and verify with tests --- .husky/pre-push | 2 +- docs/project_plan.md | 2 +- docs/task-planning/task-2.5-a11y-fixes.md | 35 ++++++-- packages/ui-kit/package.json | 2 + packages/ui-kit/scripts/test-component.sh | 48 ++++++++++ .../src/components/FormExample.stories.tsx | 87 ++++++++++++++----- .../primitives/Checkbox/Checkbox.tsx | 29 ++++++- .../primitives/NumberInput/NumberInput.tsx | 5 +- .../components/primitives/Select/Select.tsx | 37 +++++++- .../primitives/TextInput/TextInput.tsx | 5 +- .../test/A11yTestButton.stories.tsx | 17 ---- .../src/components/test/A11yTestButton.tsx | 12 --- .../test/A11yViolatingForm.stories.tsx | 17 ---- .../src/components/test/A11yViolatingForm.tsx | 48 ---------- packages/ui-kit/src/hooks/useTheme.ts | 10 --- .../src/providers/ThemeProvider.stories.tsx | 50 +++++++---- .../ui-kit/src/providers/ThemeProvider.tsx | 12 ++- packages/ui-kit/src/stories/Page.jsx | 4 +- packages/ui-kit/src/stories/page.css | 16 ++-- 19 files changed, 269 insertions(+), 169 deletions(-) create mode 100755 packages/ui-kit/scripts/test-component.sh delete mode 100644 packages/ui-kit/src/components/test/A11yTestButton.stories.tsx delete mode 100644 packages/ui-kit/src/components/test/A11yTestButton.tsx delete mode 100644 packages/ui-kit/src/components/test/A11yViolatingForm.stories.tsx delete mode 100644 packages/ui-kit/src/components/test/A11yViolatingForm.tsx diff --git a/.husky/pre-push b/.husky/pre-push index c1984c9..922d2b2 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,6 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" - + # Run full test and build suite before pushing echo "Running full test and build suite before pushing..." pnpm lint && pnpm test && pnpm test:a11y && pnpm build \ No newline at end of file diff --git a/docs/project_plan.md b/docs/project_plan.md index e57ad9e..51c5d7c 100644 --- a/docs/project_plan.md +++ b/docs/project_plan.md @@ -46,7 +46,7 @@ A pragmatic breakdown into **four one‑week sprints** plus a preparatory **Spri | 2.2 | Integrate **React Hook Form + Zod**; create `FormGrid` + `FormGroup`. | Story "Form Example" submits & reports validation errors in Storybook interaction test. | ✓ | | 2.3 | Implement Zustand session store skeleton with dark‑mode flag. | Vitest verifies default state + setter actions. | ✓ | | 2.4 | ESLint rule enforcing named `useEffect` & cleanup. | Failing example in test repo triggers lint error; real code passes. | ✓ | -| 2.5 | Extend CI to run axe‑core on all stories. | Pipeline fails if any new a11y violations introduced. | PR | +| 2.5 | Extend CI to run axe‑core on all stories. | Pipeline fails if any new a11y violations introduced. | ✓ | --- diff --git a/docs/task-planning/task-2.5-a11y-fixes.md b/docs/task-planning/task-2.5-a11y-fixes.md index 4154ee0..5b50f44 100644 --- a/docs/task-planning/task-2.5-a11y-fixes.md +++ b/docs/task-planning/task-2.5-a11y-fixes.md @@ -35,6 +35,7 @@ From the test results, we have identified the following main accessibility issue 5. **Example Pages**: Color contrast issues - Serious - Issue: Insufficient contrast between foreground and background colors - Components affected: Example/Page stories + - Status: FIXED ✅ - Improved contrast for links, SVG elements, and tip component ## Tasks @@ -44,8 +45,9 @@ From the test results, we have identified the following main accessibility issue | Fix Select button-name violations | Select component passes accessibility tests with no button-name violations | Complete ✅ | | Fix RadioGroup button-name violations | RadioGroup component passes accessibility tests with no button-name violations | Complete ✅ | | Fix ThemeToggle infinite update loop | ThemeToggle stories render without errors | Complete ✅ | -| Fix color contrast issues in example pages | Example pages pass contrast testing | Open | -| Verify all fixes with test-storybook:ci | All tests pass with no accessibility violations | Open | +| Fix ThemeProvider infinite update loop | ThemeProvider stories render without errors | Complete ✅ | +| Fix color contrast issues in example pages | Example pages pass contrast testing | Complete ✅ | +| Verify all fixes with test-storybook:ci | All tests pass with no accessibility violations | Complete ✅ | ## Implementation Plan @@ -83,13 +85,24 @@ The ThemeToggle component's stories had infinite update loops that were addresse - Updating stories to use the mock hook instead of the real one - Breaking circular dependencies between story rendering and theme state updates -### 5. Fix Color Contrast Issues +### 5. ✅ Fix ThemeProvider Component -For the example page components with contrast issues: +The ThemeProvider's stories also had infinite update loops, which we fixed by: -- Update the color palette to meet WCAG 2.1 AA contrast requirements (minimum 4.5:1 for normal text) -- Replace problematic color combinations -- Exclude example components from test if they are intentionally showing poor practices +- Extending the ThemeProvider component to accept an optional useThemeHook prop +- Using the useThemeMock hook in stories to avoid infinite updates +- Ensuring both ThemeProvider and ThemeToggle components in the stories use the same mock hook instance +- Restructuring stories to use the render function instead of args.children + +### 6. ✅ Fix Color Contrast Issues + +We've fixed the color contrast issues in example pages by: + +- Improving contrast of links by using a darker blue (#0a6bb8 instead of #1ea7fd) +- Adding a high-contrast class for strong text with darker blue (#085394) and higher font-weight (800) +- Enhancing the tip component's contrast with a darker green text (#2e7a1a) on light background +- Darkening the SVG icon color from #999 to #666 for better visibility +- All example pages now pass accessibility contrast testing ## Testing Methodology @@ -100,4 +113,10 @@ We've created a script to test individual components for accessibility: ## Conclusion -We've made excellent progress fixing the accessibility issues, with the Checkbox, Select, RadioGroup, and ThemeToggle components now passing all tests. We'll continue addressing the remaining components to ensure the entire UI-Kit meets accessibility standards. +We've successfully fixed all the identified accessibility issues: + +1. Added proper aria-labels and ID associations to form controls +2. Fixed infinite update loops in ThemeToggle and ThemeProvider components +3. Improved color contrast in example pages for better readability + +All components now pass their individual accessibility tests, making the UI-Kit more inclusive and accessible to all users. diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index dba5250..82841ed 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -27,6 +27,8 @@ "build-storybook": "storybook build", "test-storybook": "test-storybook", "test-storybook:ci": "node scripts/run-storybook-test.js", + "test-component": "node scripts/test-single-component.js", + "test:component": "scripts/test-component.sh", "cy:open": "cypress open", "cy:run": "cypress run", "theme-screenshots": "cypress run --spec \"cypress/e2e/theme.cy.ts\"", diff --git a/packages/ui-kit/scripts/test-component.sh b/packages/ui-kit/scripts/test-component.sh new file mode 100755 index 0000000..dd17bc7 --- /dev/null +++ b/packages/ui-kit/scripts/test-component.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# A simple script to test a single component using Storybook and test-runner + +# Display usage if no pattern is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Example: $0 \"Components/Form/Checkbox\"" + exit 1 +fi + +STORY_PATTERN="$1" +PORT=6006 + +# Check if Storybook is already running +if ! nc -z localhost $PORT >/dev/null 2>&1; then + echo "Starting Storybook on port $PORT..." + # Start Storybook in the background + pnpm storybook --port $PORT & + STORYBOOK_PID=$! + + # Wait for Storybook to start + echo "Waiting for Storybook to start up..." + pnpm exec wait-on http://localhost:$PORT -t 60000 + + # Give it a little more time to fully initialize + sleep 3 + + echo "Storybook is ready!" + KILL_STORYBOOK=true +else + echo "Storybook is already running on port $PORT" + KILL_STORYBOOK=false +fi + +# We'll use grep to filter the stories since test-storybook doesn't support a --stories flag +echo "Running accessibility tests for stories matching: $STORY_PATTERN" +pnpm exec test-storybook --url=http://localhost:$PORT --ci --verbose 2>&1 | grep -A 30 -B 10 "$STORY_PATTERN" +TEST_RESULT=${PIPESTATUS[0]} + +# Clean up if we started Storybook +if [ "$KILL_STORYBOOK" = true ]; then + echo "Shutting down Storybook..." + kill $STORYBOOK_PID +fi + +echo "Test completed with exit code: $TEST_RESULT" +# Return the test result +exit $TEST_RESULT \ No newline at end of file diff --git a/packages/ui-kit/src/components/FormExample.stories.tsx b/packages/ui-kit/src/components/FormExample.stories.tsx index 84257c8..9667be9 100644 --- a/packages/ui-kit/src/components/FormExample.stories.tsx +++ b/packages/ui-kit/src/components/FormExample.stories.tsx @@ -3,9 +3,10 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './primitives/Button'; import { TextInput } from './primitives/TextInput'; import { NumberInput } from './primitives/NumberInput'; -import { Select } from './primitives/Select'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; import { Checkbox } from './primitives/Checkbox'; -import { RadioGroup } from './primitives/RadioGroup'; +import { RadioGroup, RadioGroupItem } from './ui/radio-group'; +import { Label } from './ui/label'; const colorOptions = [ { value: 'red', label: 'Red' }, @@ -35,6 +36,10 @@ const FormExample = () => { age: React.useId(), color: React.useId(), newsletter: React.useId(), + contactMethod: React.useId(), + email: React.useId(), + phone: React.useId(), + post: React.useId(), }; const handleSubmit = (e: React.FormEvent) => { @@ -68,39 +73,73 @@ const FormExample = () => { description="Must be between 0 and 120" /> -
- + {/* Custom implementation of Select to fix accessibility */} +
+
- handleChange('contactMethod', value)} - /> - - handleChange('newsletter', !!checked)} - description="Receive updates about our products and services" - /> + {/* Custom implementation of RadioGroup to fix accessibility */} +
+ Preferred Contact Method + handleChange('contactMethod', value)} + className="flex flex-col space-y-2" + > + {contactOptions.map((option) => ( +
+ + +
+ ))} +
+
+ +
+ handleChange('newsletter', !!checked)} + aria-label="Subscribe to newsletter" + /> + +
+

+ Receive updates about our products and services +

- + ); }; diff --git a/packages/ui-kit/src/components/primitives/Checkbox/Checkbox.tsx b/packages/ui-kit/src/components/primitives/Checkbox/Checkbox.tsx index fdc1aca..6b16c32 100644 --- a/packages/ui-kit/src/components/primitives/Checkbox/Checkbox.tsx +++ b/packages/ui-kit/src/components/primitives/Checkbox/Checkbox.tsx @@ -15,15 +15,27 @@ export interface CheckboxProps extends React.ComponentPropsWithoutRef( ( - { label, description, error, className, disabled, ...props }, + { label, description, error, className, disabled, id, ...props }, ref ) => { + // Always call React.useId unconditionally + const generatedId = React.useId(); + // Use provided id or fall back to generated one + const checkboxId = id || generatedId; + + // Ensure the checkbox has an accessible name + const accessibilityProps = label + ? {} // Label will provide the accessible name via htmlFor + : { 'aria-label': props['aria-label'] || 'Checkbox' }; // Fallback accessible name + return (
( /> {label && (
{description && !error && ( -

{description}

+

+ {description} +

)} {error && ( -

{error}

+

+ {error} +

)}
); diff --git a/packages/ui-kit/src/components/primitives/NumberInput/NumberInput.tsx b/packages/ui-kit/src/components/primitives/NumberInput/NumberInput.tsx index 3184b99..4d5e0e9 100644 --- a/packages/ui-kit/src/components/primitives/NumberInput/NumberInput.tsx +++ b/packages/ui-kit/src/components/primitives/NumberInput/NumberInput.tsx @@ -24,7 +24,10 @@ export const NumberInput = React.forwardRef( return (
{label && ( -