From 8d95b11005debc6ae4fb16291f20da78bb5b7b80 Mon Sep 17 00:00:00 2001 From: Mohamed Meabed Date: Sat, 6 Sep 2025 17:35:20 -0400 Subject: [PATCH 1/3] chore: update code and test --- README.md | 383 +++++++++++++++++++++++++++++++----- jest.config.js | 28 +++ jest.setup.js | 41 ++++ package.json | 15 +- src/index.test.tsx | 238 ++++++++++++++++++++++ src/index.tsx | 216 ++++++++++++++++---- src/jest-dom.d.ts | 1 + yarn.lock | 478 +++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 1282 insertions(+), 118 deletions(-) create mode 100644 jest.config.js create mode 100644 jest.setup.js create mode 100644 src/index.test.tsx create mode 100644 src/jest-dom.d.ts diff --git a/README.md b/README.md index 2164a88b..45fd39a1 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,374 @@ # @devmehq/react-qr-code -Simple & Advanced React component to generate [QR codes](http://en.wikipedia.org/wiki/QR_code) - +๐ŸŽฏ Simple & Advanced React component to generate [QR codes](http://en.wikipedia.org/wiki/QR_code) with custom styling, multiple render formats, and image embedding support. [![NPM version](https://badgen.net/npm/v/@devmehq/react-qr-code)](https://npm.im/@devmehq/react-qr-code) [![Build Status](https://github.com/devmehq/react-qr-code/workflows/CI/badge.svg)](https://github.com/devmehq/react-qr-code/actions) [![Downloads](https://img.shields.io/npm/dm/@devmehq/react-qr-code.svg)](https://www.npmjs.com/package/@devmehq/react-qr-code) +[![Bundle Size](https://badgen.net/bundlephobia/minzip/@devmehq/react-qr-code)](https://bundlephobia.com/package/@devmehq/react-qr-code) +[![License](https://badgen.net/npm/license/@devmehq/react-qr-code)](https://github.com/devmehq/react-qr-code/blob/master/LICENSE.md) [![UNPKG](https://img.shields.io/badge/UNPKG-OK-179BD7.svg)](https://unpkg.com/browse/@devmehq/react-qr-code@latest/) [![Edit react-qr-code-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-qr-code-demo-ccho5l?fontsize=14&hidenavigation=1&theme=dark) -## Installation -```npm +## โœจ Features + +- ๐ŸŽจ **Customizable**: Colors, sizes, margins, and styles +- ๐Ÿ–ผ๏ธ **Multiple Formats**: Render as SVG or Canvas +- ๐Ÿ“ฑ **Responsive**: Scales perfectly on all devices +- ๐Ÿž๏ธ **Image Embedding**: Add logos or images to QR codes +- ๐Ÿ›ก๏ธ **Error Correction**: Four levels (L, M, Q, H) +- ๐Ÿ“ฆ **Lightweight**: Zero dependencies, small bundle size +- ๐Ÿ”ง **TypeScript**: Full TypeScript support +- โšก **Performance**: Optimized rendering with React hooks +## ๐Ÿ“ฆ Installation + +```bash +# Using npm npm install @devmehq/react-qr-code + +# Using yarn +yarn add @devmehq/react-qr-code + +# Using pnpm +pnpm add @devmehq/react-qr-code +``` + +## ๐Ÿš€ Quick Start + +### Basic Usage + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; + +function App() { + return ( + + ); +} +``` + +### With Custom Styling + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; + +function StyledQRCode() { + return ( + + ); +} +``` + +### Canvas Rendering + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; + +function CanvasQRCode() { + return ( + + ); +} ``` -```yarn -yarn install @devmehq/react-qr-code +### With Logo/Image + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; + +function QRCodeWithLogo() { + return ( + + ); +} +``` + +## ๐Ÿ“– API Reference + +### ReactQrCode Props + +| Prop | Type | Default | Description | +|--------------|------------------------------|---------------|------------------------------------------------------| +| `value` | `string` | **Required** | The value to encode in the QR code | +| `renderAs` | `'svg' \| 'canvas'` | `'svg'` | Render format (SVG or Canvas) | +| `size` | `number` | `256` | Size of the QR code in pixels | +| `bgColor` | `string` | `'#ffffff'` | Background color (CSS color value) | +| `fgColor` | `string` | `'#000000'` | Foreground color (CSS color value) | +| `level` | `'L' \| 'M' \| 'Q' \| 'H'` | `'L'` | Error correction level | +| `marginSize` | `number` | `0` | Margin around the QR code in pixels | +| `style` | `CSSProperties` | `undefined` | React style object | +| `className` | `string` | `undefined` | CSS class name | +| `title` | `string` | `undefined` | Title for SVG accessibility | +| `id` | `string` | `undefined` | HTML id attribute | +| `images` | `ReactQrCodeImageProps[]` | `undefined` | Array of images to embed in the QR code | + +### ReactQrCodeImageProps + +| Property | Type | Default | Description | +|------------|-----------|-------------------|------------------------------------------------| +| `src` | `string` | **Required** | Image source URL | +| `x` | `number` | Auto-centered | X position of the image | +| `y` | `number` | Auto-centered | Y position of the image | +| `height` | `number` | 10% of QR size | Height of the image | +| `width` | `number` | 10% of QR size | Width of the image | +| `excavate` | `boolean` | `false` | Whether to clear QR modules behind the image | + +### Error Correction Levels + +| Level | Error Correction | Data Capacity | +|-------|-----------------|---------------| +| `L` | ~7% | High | +| `M` | ~15% | Medium | +| `Q` | ~25% | Medium-Low | +| `H` | ~30% | Low | + +## ๐ŸŽจ Styling & Customization + +### Responsive Design + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; + +function ResponsiveQRCode() { + return ( +
+ +
+ ); +} ``` -## Usage +### Dark Mode Support -```typescript +```tsx import React from 'react'; import { ReactQrCode } from '@devmehq/react-qr-code'; - +function DarkModeQRCode({ isDarkMode }) { + return ( + + ); +} ``` -```js -var React = require('react'); -var { ReactQrCode } = require('@devmehq/react-qr-code'); +### Custom CSS Classes + +```tsx +import React from 'react'; +import { ReactQrCode } from '@devmehq/react-qr-code'; +import './styles.css'; - +function CustomStyledQRCode() { + return ( + + ); +} ``` -## Available Props +```css +/* styles.css */ +.qr-code-custom { + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} +``` -| prop | type | default value | -|--------------|------------------------------|---------------| -| `value` | `string` | | -| `renderAs` | `string` (`'canvas' 'svg'`) | `'canvas'` | -| `size` | `number` | `128` | -| `bgColor` | `string` (CSS color) | `"#FFFFFF"` | -| `fgColor` | `string` (CSS color) | `"#000000"` | -| `level` | `string` (`'L' 'M' 'Q' 'H'`) | `'L'` | -| `marginSize` | `number` | `false` | -| `images` | `array` (see below) | | +**Note:** When using `renderAs="canvas"` on high-density displays, the canvas is scaled for pixel-perfect rendering. Custom styles are merged with internal scaling styles. -### `imageSettings` +## ๐Ÿ’ก Use Cases -| field | type | default value | -|------------|-----------|-------------------| -| `src` | `string` | | -| `x` | `number` | none, will center | -| `y` | `number` | none, will center | -| `height` | `number` | 10% of `size` | -| `width` | `number` | 10% of `size` | -| `excavate` | `boolean` | `false` | +### WiFi Password Sharing -## Custom Styles +```tsx +function WiFiQRCode({ ssid, password, security = 'WPA' }) { + const wifiString = `WIFI:T:${security};S:${ssid};P:${password};;`; + + return ( + + ); +} +``` -`@devmehq/react-qr-code` will pass through any additional props to the underlying DOM node (`` or ``). This allows the use of inline `style` or custom `className` to customize the rendering. One common use would be to support a responsive layout. +### Contact Information (vCard) -**Note:** In order to render QR Codes in `` on high density displays, we scale the canvas element to contain an appropriate number of pixels and then use inline styles to scale back down. We will merge any additional styles, with custom `height` and `width` overriding our own values. This allows scaling to percentages *but* if scaling beyond the `size`, you will encounter blurry images. I recommend detecting resizes with something like [react-measure](https://github.com/souporserious/react-measure) to detect and pass the appropriate size when rendering to ``. +```tsx +function ContactQRCode({ name, phone, email }) { + const vCard = `BEGIN:VCARD +VERSION:3.0 +FN:${name} +TEL:${phone} +EMAIL:${email} +END:VCARD`; + + return ( + + ); +} +``` + +### Two-Factor Authentication + +```tsx +function TwoFactorQRCode({ secret, issuer, accountName }) { + const otpauth = `otpauth://totp/${issuer}:${accountName}?secret=${secret}&issuer=${issuer}`; + + return ( + + ); +} +``` + +### Payment Links + +```tsx +function PaymentQRCode({ amount, recipient, currency = 'USD' }) { + const paymentLink = `https://pay.example.com/?to=${recipient}&amount=${amount}¤cy=${currency}`; + + return ( + + ); +} +``` qrcode-demo -## TODO -- Add Image Ref -- Add Corner Images and Center Image -- Add Examples to wifi password, 2fa, and other QR codes -- ADD SSR Rendering Support -- Add Download / Share QR Code -- Add Test +## ๐Ÿงช Testing + +```bash +# Run tests +yarn test + +# Run tests in watch mode +yarn test:watch + +# Generate coverage report +yarn test:coverage +``` + +## ๐Ÿ”ง Development + +```bash +# Install dependencies +yarn install + +# Build the library +yarn build + +# Run linting +yarn lint-js + +# Format code +yarn prettier +``` + +## ๐Ÿค Contributing + +Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## ๐Ÿ“ Roadmap + +- [ ] Download QR code as image (PNG/JPEG/SVG) +- [ ] Share QR code functionality +- [ ] Server-side rendering (SSR) support +- [ ] Corner dot customization +- [ ] Gradient color support +- [ ] Custom shape modules (dots, rounded, etc.) +- [ ] Animation support +- [ ] Batch QR code generation +- [ ] QR code scanner component + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details. + +## ๐Ÿ™ Acknowledgments + +- QR Code is a registered trademark of DENSO WAVE INCORPORATED +- Built with โค๏ธ by the [DEV.ME](https://dev.me) team +- Inspired by the QR code specification and community feedback + +## ๐Ÿ“ง Support + +For support, email support@dev.me or open an issue on [GitHub](https://github.com/devmehq/react-qr-code/issues). + +--- -## LICENSE [MIT](LICENSE.md) +
+ Made with โค๏ธ by DEV.ME +
diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..fa8f563f --- /dev/null +++ b/jest.config.js @@ -0,0 +1,28 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], + transform: { + '^.+\\.(ts|tsx)$': ['ts-jest', { + tsconfig: { + jsx: 'react', + esModuleInterop: true, + allowSyntheticDefaultImports: true + } + }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/index.ts', + '!src/**/*.test.{ts,tsx}', + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + setupFilesAfterEnv: ['/jest.setup.js'], + moduleNameMapper: { + '\\.(css|less|scss|sass)$': 'identity-obj-proxy', + }, +} \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 00000000..c011d117 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,41 @@ +require('@testing-library/jest-dom') + +// Mock canvas for testing +HTMLCanvasElement.prototype.getContext = jest.fn(() => ({ + fillStyle: '', + fillRect: jest.fn(), + clearRect: jest.fn(), + getImageData: jest.fn(() => ({ + data: new Array(4), + })), + putImageData: jest.fn(), + createImageData: jest.fn(() => []), + setTransform: jest.fn(), + drawImage: jest.fn(), + save: jest.fn(), + restore: jest.fn(), + scale: jest.fn(), + rotate: jest.fn(), + translate: jest.fn(), + transform: jest.fn(), + beginPath: jest.fn(), + closePath: jest.fn(), + moveTo: jest.fn(), + lineTo: jest.fn(), + clip: jest.fn(), + quadraticCurveTo: jest.fn(), + bezierCurveTo: jest.fn(), + arc: jest.fn(), + arcTo: jest.fn(), + isPointInPath: jest.fn(), + stroke: jest.fn(), + fill: jest.fn(), +})) + +// Mock Image constructor +global.Image = class { + constructor() { + this.onload = null + this.src = '' + } +} \ No newline at end of file diff --git a/package.json b/package.json index 357b6af6..c740594f 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,9 @@ "prepare": "husky", "prepublishOnly": "rm -rf dist/* && NODE_ENV=production rollup -c rollup.config.mjs", "prettier": "prettier --write '**/*.{ts,tsx,css,scss}'", - "test": "echo \"Error: no test specified\"", - "test-watch": "jest --watch" + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "lint-staged": { "src/*.{ts,tsx,js,jsx}": "prettier --write", @@ -58,6 +59,10 @@ "@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-replace": "6.0.2", "@rollup/plugin-typescript": "12.1.4", + "@testing-library/dom": "10.4.1", + "@testing-library/jest-dom": "6.8.0", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", "@types/jasmine": "5.1.9", "@types/jest": "30.0.0", "@types/react": "19.1.12", @@ -67,10 +72,14 @@ "eslint": "9.35.0", "eslint-config-prettier": "10.1.8", "husky": "9.1.7", + "identity-obj-proxy": "3.0.0", "jest": "30.1.3", + "jest-environment-jsdom": "30.1.2", "lint-staged": "16.1.6", "prettier": "3.6.2", "pretty-quick": "4.2.2", + "react": "19.1.1", + "react-dom": "19.1.1", "rollup": "4.50.0", "rollup-plugin-uglify": "6.0.4", "ts-jest": "29.4.1", @@ -81,5 +90,5 @@ "react": ">=17.0.2", "react-dom": ">=17.0.2" }, - "packageManager": "yarn@1.22.21" + "packageManager": "yarn@1.22.22" } diff --git a/src/index.test.tsx b/src/index.test.tsx new file mode 100644 index 00000000..a40ba599 --- /dev/null +++ b/src/index.test.tsx @@ -0,0 +1,238 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import { ReactQrCode } from './index' + +describe('ReactQrCode', () => { + describe('SVG rendering', () => { + it('should render SVG QR code by default', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + expect(svg).toHaveAttribute('width', '256') + expect(svg).toHaveAttribute('height', '256') + }) + + it('should render with custom size', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toHaveAttribute('width', '512') + expect(svg).toHaveAttribute('height', '512') + }) + + it('should apply custom colors', () => { + const { container } = render( + + ) + const paths = container.querySelectorAll('path') + const hasRedPath = Array.from(paths).some( + (path) => path.getAttribute('fill') === '#FF0000' + ) + expect(hasRedPath).toBe(true) + }) + + it('should apply margin size', () => { + const { container } = render() + const rect = container.querySelector('rect') + expect(rect).toBeInTheDocument() + }) + + it('should render with title', () => { + const { container } = render( + + ) + const title = container.querySelector('title') + expect(title).toBeInTheDocument() + expect(title?.textContent).toBe('QR Code Title') + }) + + it('should apply custom className', () => { + const { container } = render( + + ) + const svg = container.querySelector('svg') + expect(svg).toHaveClass('custom-qr') + }) + + it('should apply custom styles', () => { + const { container } = render( + + ) + const svg = container.querySelector('svg') + expect(svg).toHaveStyle('border: 1px solid red') + }) + + it('should render images in SVG', () => { + const { container } = render( + + ) + const image = container.querySelector('image') + expect(image).toBeInTheDocument() + expect(image).toHaveAttribute('href', 'https://example.com/logo.png') + expect(image).toHaveAttribute('width', '24') + expect(image).toHaveAttribute('height', '24') + }) + + it('should render images with excavate', () => { + const { container } = render( + + ) + const g = container.querySelector('g') + const rect = g?.querySelector('rect') + expect(rect).toBeInTheDocument() + }) + + it('should position images with custom x and y', () => { + const { container } = render( + + ) + const image = container.querySelector('image') + expect(image).toHaveAttribute('x', '50') + expect(image).toHaveAttribute('y', '50') + }) + }) + + describe('Canvas rendering', () => { + it('should render canvas when renderAs is canvas', () => { + const { container } = render( + + ) + const canvas = container.querySelector('canvas') + expect(canvas).toBeInTheDocument() + expect(canvas).toHaveStyle('width: 256px') + expect(canvas).toHaveStyle('height: 256px') + }) + + it('should apply custom size to canvas', () => { + const { container } = render( + + ) + const canvas = container.querySelector('canvas') + expect(canvas).toHaveStyle('width: 512px') + expect(canvas).toHaveStyle('height: 512px') + }) + + it('should apply custom className to canvas', () => { + const { container } = render( + + ) + const canvas = container.querySelector('canvas') + expect(canvas).toHaveClass('canvas-qr') + }) + + it('should apply custom id to canvas', () => { + const { container } = render( + + ) + const canvas = container.querySelector('canvas') + expect(canvas).toHaveAttribute('id', 'qr-canvas') + }) + }) + + describe('Error correction levels', () => { + const levels: Array<'L' | 'M' | 'Q' | 'H'> = ['L', 'M', 'Q', 'H'] + + levels.forEach((level) => { + it(`should render with error correction level ${level}`, () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + }) + }) + + describe('Value encoding', () => { + it('should encode URLs', () => { + const { container } = render( + + ) + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should encode plain text', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should encode numbers', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should encode special characters', () => { + const { container } = render( + + ) + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should encode long text', () => { + const longText = 'A'.repeat(1000) + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + }) + + describe('Default props', () => { + it('should use default value when no value provided', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + + it('should use default colors', () => { + const { container } = render() + const paths = container.querySelectorAll('path') + const hasBlackPath = Array.from(paths).some( + (path) => path.getAttribute('fill') === '#000000' + ) + expect(hasBlackPath).toBe(true) + }) + + it('should use default size', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toHaveAttribute('width', '256') + }) + + it('should use default error correction level', () => { + const { container } = render() + const svg = container.querySelector('svg') + expect(svg).toBeInTheDocument() + }) + }) +}) diff --git a/src/index.tsx b/src/index.tsx index 8f811cfe..09234820 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,18 +1,20 @@ -import React from 'react' +import React, { useEffect, useRef, CSSProperties } from 'react' import { JsQrCode } from './qr-js/js-qr-code' import { qrErrorCorrectLevel } from './qr-js/qr-error-correct-level' +interface QRCodePathProps { + d: string + fill: string + transformX: number + transformY: number +} + function QRCodePath({ d, fill, transformX, transformY, -}: { - d: string - fill: string - transformX: number - transformY: number -}) { +}: QRCodePathProps): React.ReactElement { return ( {title ? {title} : null} @@ -42,29 +48,93 @@ function QRCodeSvg({ ) } -export type ReactQrCodeImageProps = { - // todo implement +export interface ReactQrCodeImageProps { src: string - height: number - width: number + height?: number + width?: number excavate?: boolean x?: number y?: number } -export type ReactQrCodeProps = { +export interface ReactQrCodeProps { value: string size?: number level?: 'L' | 'M' | 'Q' | 'H' bgColor?: string fgColor?: string - marginSize?: number // todo implement - style?: Record // todo implement - renderAs?: 'svg' | 'canvas' // todo implement + marginSize?: number + style?: CSSProperties + renderAs?: 'svg' | 'canvas' + images?: ReactQrCodeImageProps[] + title?: string + className?: string + id?: string +} + +function drawQRCodeCanvas( + canvas: HTMLCanvasElement, + cells: boolean[][], + size: number, + marginSize: number, + bgColor: string, + fgColor: string, images?: ReactQrCodeImageProps[] +): void { + const ctx = canvas.getContext('2d') + if (!ctx) return + + const scale = window.devicePixelRatio || 1 + canvas.width = size * scale + canvas.height = size * scale + ctx.scale(scale, scale) + + const actualSize = size - marginSize * 2 + const tileSize = actualSize / cells.length + + ctx.fillStyle = bgColor + ctx.fillRect(0, 0, size, size) + + cells.forEach((row, rowIndex) => { + row.forEach((cell, cellIndex) => { + if (cell) { + ctx.fillStyle = fgColor + const x = marginSize + cellIndex * tileSize + const y = marginSize + rowIndex * tileSize + ctx.fillRect( + Math.round(x), + Math.round(y), + Math.ceil(tileSize), + Math.ceil(tileSize) + ) + } + }) + }) + + if (images && images.length > 0) { + images.forEach((imageProps) => { + const img = new Image() + img.onload = () => { + const imgWidth = imageProps.width || size * 0.1 + const imgHeight = imageProps.height || size * 0.1 + const imgX = + imageProps.x !== undefined ? imageProps.x : (size - imgWidth) / 2 + const imgY = + imageProps.y !== undefined ? imageProps.y : (size - imgHeight) / 2 + + if (imageProps.excavate) { + ctx.fillStyle = bgColor + ctx.fillRect(imgX - 5, imgY - 5, imgWidth + 10, imgHeight + 10) + } + + ctx.drawImage(img, imgX, imgY, imgWidth, imgHeight) + } + img.src = imageProps.src + }) + } } -export function ReactQrCode(props: ReactQrCodeProps) { +export function ReactQrCode(props: ReactQrCodeProps): React.ReactElement { const { bgColor = '#ffffff', fgColor = '#000000', @@ -73,27 +143,78 @@ export function ReactQrCode(props: ReactQrCodeProps) { value = 'https://github.com/devmehq/react-qr-code', marginSize = 0, renderAs = 'svg', + images, + title, + style, + className, + id, ...rest } = props - // We'll use type === -1 to force JsQrCode to automatically pick the best type. + + const canvasRef = useRef(null) + const qrcode = new JsQrCode(-1, qrErrorCorrectLevel[level]) qrcode.addData(value) qrcode.make() const cells = qrcode.modules - const tileSize = size / cells.length + + useEffect(() => { + if (renderAs === 'canvas' && canvasRef.current) { + drawQRCodeCanvas( + canvasRef.current, + cells, + size, + marginSize, + bgColor, + fgColor, + images + ) + } + }, [renderAs, cells, size, marginSize, bgColor, fgColor, images]) + + if (renderAs === 'canvas') { + return ( + + ) + } + + const actualSize = size - marginSize * 2 + const tileSize = actualSize / cells.length + return ( - - {cells.map((row: any[], rowIndex: number) => + + {marginSize > 0 && ( + + )} + {cells.map((row: boolean[], rowIndex: number) => row.map((cell, cellIndex) => { const fill = cell ? fgColor : bgColor - const transformX = Math.round(cellIndex * tileSize) - const transformY = Math.round(rowIndex * tileSize) + const transformX = marginSize + Math.round(cellIndex * tileSize) + const transformY = marginSize + Math.round(rowIndex * tileSize) const qrItemWidth = - Math.round((cellIndex + 1) * tileSize) - transformX + Math.round((cellIndex + 1) * tileSize) - + Math.round(cellIndex * tileSize) const qrItemHeight = - Math.round((rowIndex + 1) * tileSize) - transformY + Math.round((rowIndex + 1) * tileSize) - + Math.round(rowIndex * tileSize) const d = `M 0 0 L ${qrItemWidth} 0 L ${qrItemWidth} ${qrItemHeight} L 0 ${qrItemHeight} Z` - return ( + return cell ? ( - ) + ) : null }) )} + {images?.map((imageProps, index) => { + const imgWidth = imageProps.width || size * 0.1 + const imgHeight = imageProps.height || size * 0.1 + const imgX = + imageProps.x !== undefined ? imageProps.x : (size - imgWidth) / 2 + const imgY = + imageProps.y !== undefined ? imageProps.y : (size - imgHeight) / 2 + + return ( + + {imageProps.excavate && ( + + )} + + + ) + })} ) } diff --git a/src/jest-dom.d.ts b/src/jest-dom.d.ts new file mode 100644 index 00000000..c44951a6 --- /dev/null +++ b/src/jest-dom.d.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/yarn.lock b/yarn.lock index a11ac8a7..1b00de9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,23 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": +"@adobe/css-tools@^4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9" + integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== + +"@asamuzakjp/css-color@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz#cc42f5b85c593f79f1fa4f25d2b9b321e61d1794" + integrity sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw== + dependencies: + "@csstools/css-calc" "^2.1.3" + "@csstools/css-color-parser" "^3.0.9" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + lru-cache "^10.4.3" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== @@ -235,6 +251,11 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/runtime@^7.12.5": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + "@babel/template@^7.27.2": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" @@ -270,6 +291,34 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== + +"@csstools/css-calc@^2.1.3", "@csstools/css-calc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" + integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + +"@csstools/css-color-parser@^3.0.9": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== + dependencies: + "@csstools/color-helpers" "^5.1.0" + "@csstools/css-calc" "^2.1.4" + +"@csstools/css-parser-algorithms@^3.0.4": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + +"@csstools/css-tokenizer@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + "@emnapi/core@^1.4.3": version "1.5.0" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" @@ -292,10 +341,6 @@ dependencies: tslib "^2.4.0" -"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz#0e3b5e45566d1bce1ec47d8aae2fc2ad77ad0894" - integrity sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q== "@eslint-community/eslint-utils@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" @@ -482,6 +527,19 @@ resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== +"@jest/environment-jsdom-abstract@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.1.2.tgz#4ede626bf27a52c21f11917608f4ab539363b16f" + integrity sha512-u8kTh/ZBl97GOmnGJLYK/1GuwAruMC4hoP6xuk/kwltmVWsA9u/6fH1/CsPVGt2O+Wn2yEjs8n1B1zZJ62Cx0w== + dependencies: + "@jest/environment" "30.1.2" + "@jest/fake-timers" "30.1.2" + "@jest/types" "30.0.5" + "@types/jsdom" "^21.1.7" + "@types/node" "*" + jest-mock "30.0.5" + jest-util "30.0.5" + "@jest/environment@30.1.2": version "30.1.2" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.1.2.tgz#f1bd73a7571f96104a3ff2007747c2ce12b5c038" @@ -492,13 +550,6 @@ "@types/node" "*" jest-mock "30.0.5" -"@jest/expect-utils@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" - integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== - dependencies: - "@jest/get-type" "30.0.1" - "@jest/expect-utils@30.1.2": version "30.1.2" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.1.2.tgz#88ea18040f707c9fadb6fd9e77568cae5266cee8" @@ -905,6 +956,44 @@ dependencies: "@sinonjs/commons" "^3.0.1" +"@testing-library/dom@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95" + integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + picocolors "1.1.1" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz#697db9424f0d21d8216f1958fa0b1b69b5f43923" + integrity sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + picocolors "^1.1.1" + redent "^3.0.0" + +"@testing-library/react@16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6" + integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + dependencies: + "@babel/runtime" "^7.12.5" + +"@testing-library/user-event@14.6.1": + version "14.6.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" + integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + "@tybys/wasm-util@^0.10.0": version "0.10.0" resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369" @@ -912,6 +1001,11 @@ dependencies: tslib "^2.4.0" +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -982,6 +1076,15 @@ expect "^30.0.0" pretty-format "^30.0.0" +"@types/jsdom@^21.1.7": + version "21.1.7" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.7.tgz#9edcb09e0b07ce876e7833922d3274149c898cfa" + integrity sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1016,6 +1119,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -1238,6 +1346,11 @@ acorn@^8.15.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1279,7 +1392,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.2.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -1309,6 +1422,18 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + babel-jest@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.1.2.tgz#decd53b3a0cafca49443f93fb7a2c0fba55510da" @@ -1555,18 +1680,44 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +cssstyle@^4.2.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.6.0.tgz#ea18007024e3167f4f105315f3ec2d982bf48ed9" + integrity sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg== + dependencies: + "@asamuzakjp/css-color" "^3.2.0" + rrweb-cssom "^0.8.0" + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1: +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: ms "^2.1.3" +decimal.js@^10.5.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + dedent@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" @@ -1582,11 +1733,26 @@ deepmerge@^4.2.2, deepmerge@^4.3.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + detect-newline@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1617,6 +1783,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + environment@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" @@ -1777,7 +1948,6 @@ exit-x@^0.2.2: integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== expect@30.1.2, expect@^30.0.0: -expect@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/expect/-/expect-30.1.2.tgz#094909c2443f76b9e208fafac4a315aaaf924580" integrity sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg== @@ -1990,6 +2160,11 @@ handlebars@^4.7.8: optionalDependencies: uglify-js "^3.1.4" +harmony-reflect@^1.4.6: + version "1.6.2" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" + integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2007,11 +2182,34 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +https-proxy-agent@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -2022,6 +2220,20 @@ husky@9.1.7: resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +identity-obj-proxy@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== + dependencies: + harmony-reflect "^1.4.6" + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -2053,6 +2265,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2122,6 +2339,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-reference@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -2271,16 +2493,6 @@ jest-config@30.1.3: slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@30.0.5: - version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" - integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== - dependencies: - "@jest/diff-sequences" "30.0.1" - "@jest/get-type" "30.0.1" - chalk "^4.1.2" - pretty-format "30.0.5" - jest-diff@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.1.2.tgz#8ff4217e5b63fef49a5b37462999d8f5299a4eb4" @@ -2309,6 +2521,17 @@ jest-each@30.1.0: jest-util "30.0.5" pretty-format "30.0.5" +jest-environment-jsdom@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-30.1.2.tgz#30756e73b9b7c10ebbe9c066f30dd1af670eb9c1" + integrity sha512-LXsfAh5+mDTuXDONGl1ZLYxtJEaS06GOoxJb2arcJTjIfh1adYg8zLD8f6P0df8VmjvCaMrLmc1PgHUI/YUTbg== + dependencies: + "@jest/environment" "30.1.2" + "@jest/environment-jsdom-abstract" "30.1.2" + "@types/jsdom" "^21.1.7" + "@types/node" "*" + jsdom "^26.1.0" + jest-environment-node@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.1.2.tgz#ae2f20442f8abc3c6b20120dc789fa38faff568f" @@ -2348,16 +2571,6 @@ jest-leak-detector@30.1.0: "@jest/get-type" "30.1.0" pretty-format "30.0.5" -jest-matcher-utils@30.0.5: - version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" - integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== - dependencies: - "@jest/get-type" "30.0.1" - chalk "^4.1.2" - jest-diff "30.0.5" - pretty-format "30.0.5" - jest-matcher-utils@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz#3f1b63949f740025aff740c6c6a1b653ae370fbb" @@ -2594,6 +2807,32 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^26.1.0: + version "26.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.1.0.tgz#ab5f1c1cafc04bd878725490974ea5e8bf0c72b3" + integrity sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== + dependencies: + cssstyle "^4.2.1" + data-urls "^5.0.0" + decimal.js "^10.5.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.6" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.16" + parse5 "^7.2.1" + rrweb-cssom "^0.8.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.1.1" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.1.1" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -2717,7 +2956,7 @@ log-update@^6.1.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" -lru-cache@^10.2.0: +lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -2729,6 +2968,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + magic-string@^0.30.3: version "0.30.18" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" @@ -2783,6 +3027,11 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2859,6 +3108,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +nwsapi@^2.2.16: + version "2.2.22" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.22.tgz#109f9530cda6c156d6a713cdf5939e9f0de98b9d" + integrity sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2947,6 +3201,13 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5@^7.0.0, parse5@^7.2.1: + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2975,7 +3236,7 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -picocolors@^1.1.1: +picocolors@1.1.1, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -3026,6 +3287,15 @@ pretty-format@30.0.5, pretty-format@^30.0.0: ansi-styles "^5.2.0" react-is "^18.3.1" +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-quick@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-4.2.2.tgz#0fc31da666f182fe14e119905fc9829b5b85a234" @@ -3039,7 +3309,7 @@ pretty-quick@4.2.2: tinyexec "^0.3.2" tslib "^2.8.1" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -3054,11 +3324,36 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-dom@19.1.1: + version "19.1.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893" + integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw== + dependencies: + scheduler "^0.26.0" + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +react@19.1.1: + version "19.1.1" + resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af" + integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ== + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -3148,6 +3443,11 @@ rollup@4.50.0: "@rollup/rollup-win32-x64-msvc" "4.50.0" fsevents "~2.3.2" +rrweb-cssom@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz#3021d1b4352fbf3b614aaeed0bc0d5739abe0bc2" + integrity sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -3155,6 +3455,23 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" + integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== + semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -3302,6 +3619,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -3333,6 +3657,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + synckit@^0.11.8: version "0.11.11" resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" @@ -3354,6 +3683,18 @@ tinyexec@^0.3.2: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== +tldts-core@^6.1.86: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" + integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== + +tldts@^6.1.32: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" + integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== + dependencies: + tldts-core "^6.1.86" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3366,6 +3707,20 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" + integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== + dependencies: + tldts "^6.1.32" + +tr46@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" + integrity sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== + dependencies: + punycode "^2.3.1" + ts-api-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" @@ -3479,6 +3834,13 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -3486,6 +3848,31 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0, whatwg-url@^14.1.1: + version "14.2.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.2.0.tgz#4ee02d5d725155dae004f6ae95c73e7ef5d95663" + integrity sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== + dependencies: + tr46 "^5.1.0" + webidl-conversions "^7.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -3543,6 +3930,21 @@ write-file-atomic@^5.0.1: imurmurhash "^0.1.4" signal-exit "^4.0.1" +ws@^8.18.0: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" From a26c99fafb5455f7967f81ec48482ebb491ce72f Mon Sep 17 00:00:00 2001 From: Mohamed Meabed Date: Sat, 6 Sep 2025 17:35:28 -0400 Subject: [PATCH 2/3] chore: update code and test --- README.md | 145 +++++++++++++++++++++---------------------------- jest.config.js | 19 ++++--- jest.setup.js | 2 +- 3 files changed, 74 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 45fd39a1..b1c6a8ce 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ - ๐Ÿ“ฆ **Lightweight**: Zero dependencies, small bundle size - ๐Ÿ”ง **TypeScript**: Full TypeScript support - โšก **Performance**: Optimized rendering with React hooks + ## ๐Ÿ“ฆ Installation ```bash @@ -39,21 +40,19 @@ pnpm add @devmehq/react-qr-code ### Basic Usage ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function App() { - return ( - - ); + return } ``` ### With Custom Styling ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function StyledQRCode() { return ( @@ -67,15 +66,15 @@ function StyledQRCode() { style={{ borderRadius: '8px' }} className="shadow-lg" /> - ); + ) } ``` ### Canvas Rendering ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function CanvasQRCode() { return ( @@ -84,15 +83,15 @@ function CanvasQRCode() { renderAs="canvas" size={256} /> - ); + ) } ``` ### With Logo/Image ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function QRCodeWithLogo() { return ( @@ -108,7 +107,7 @@ function QRCodeWithLogo() { }, ]} /> - ); + ) } ``` @@ -116,48 +115,48 @@ function QRCodeWithLogo() { ### ReactQrCode Props -| Prop | Type | Default | Description | -|--------------|------------------------------|---------------|------------------------------------------------------| -| `value` | `string` | **Required** | The value to encode in the QR code | -| `renderAs` | `'svg' \| 'canvas'` | `'svg'` | Render format (SVG or Canvas) | -| `size` | `number` | `256` | Size of the QR code in pixels | -| `bgColor` | `string` | `'#ffffff'` | Background color (CSS color value) | -| `fgColor` | `string` | `'#000000'` | Foreground color (CSS color value) | -| `level` | `'L' \| 'M' \| 'Q' \| 'H'` | `'L'` | Error correction level | -| `marginSize` | `number` | `0` | Margin around the QR code in pixels | -| `style` | `CSSProperties` | `undefined` | React style object | -| `className` | `string` | `undefined` | CSS class name | -| `title` | `string` | `undefined` | Title for SVG accessibility | -| `id` | `string` | `undefined` | HTML id attribute | -| `images` | `ReactQrCodeImageProps[]` | `undefined` | Array of images to embed in the QR code | +| Prop | Type | Default | Description | +| ------------ | -------------------------- | ------------ | --------------------------------------- | +| `value` | `string` | **Required** | The value to encode in the QR code | +| `renderAs` | `'svg' \| 'canvas'` | `'svg'` | Render format (SVG or Canvas) | +| `size` | `number` | `256` | Size of the QR code in pixels | +| `bgColor` | `string` | `'#ffffff'` | Background color (CSS color value) | +| `fgColor` | `string` | `'#000000'` | Foreground color (CSS color value) | +| `level` | `'L' \| 'M' \| 'Q' \| 'H'` | `'L'` | Error correction level | +| `marginSize` | `number` | `0` | Margin around the QR code in pixels | +| `style` | `CSSProperties` | `undefined` | React style object | +| `className` | `string` | `undefined` | CSS class name | +| `title` | `string` | `undefined` | Title for SVG accessibility | +| `id` | `string` | `undefined` | HTML id attribute | +| `images` | `ReactQrCodeImageProps[]` | `undefined` | Array of images to embed in the QR code | ### ReactQrCodeImageProps -| Property | Type | Default | Description | -|------------|-----------|-------------------|------------------------------------------------| -| `src` | `string` | **Required** | Image source URL | -| `x` | `number` | Auto-centered | X position of the image | -| `y` | `number` | Auto-centered | Y position of the image | -| `height` | `number` | 10% of QR size | Height of the image | -| `width` | `number` | 10% of QR size | Width of the image | -| `excavate` | `boolean` | `false` | Whether to clear QR modules behind the image | +| Property | Type | Default | Description | +| ---------- | --------- | -------------- | -------------------------------------------- | +| `src` | `string` | **Required** | Image source URL | +| `x` | `number` | Auto-centered | X position of the image | +| `y` | `number` | Auto-centered | Y position of the image | +| `height` | `number` | 10% of QR size | Height of the image | +| `width` | `number` | 10% of QR size | Width of the image | +| `excavate` | `boolean` | `false` | Whether to clear QR modules behind the image | ### Error Correction Levels | Level | Error Correction | Data Capacity | -|-------|-----------------|---------------| -| `L` | ~7% | High | -| `M` | ~15% | Medium | -| `Q` | ~25% | Medium-Low | -| `H` | ~30% | Low | +| ----- | ---------------- | ------------- | +| `L` | ~7% | High | +| `M` | ~15% | Medium | +| `Q` | ~25% | Medium-Low | +| `H` | ~30% | Low | ## ๐ŸŽจ Styling & Customization ### Responsive Design ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function ResponsiveQRCode() { return ( @@ -168,15 +167,15 @@ function ResponsiveQRCode() { style={{ width: '100%', height: 'auto' }} /> - ); + ) } ``` ### Dark Mode Support ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' function DarkModeQRCode({ isDarkMode }) { return ( @@ -185,16 +184,16 @@ function DarkModeQRCode({ isDarkMode }) { bgColor={isDarkMode ? '#1f2937' : '#ffffff'} fgColor={isDarkMode ? '#f3f4f6' : '#000000'} /> - ); + ) } ``` ### Custom CSS Classes ```tsx -import React from 'react'; -import { ReactQrCode } from '@devmehq/react-qr-code'; -import './styles.css'; +import React from 'react' +import { ReactQrCode } from '@devmehq/react-qr-code' +import './styles.css' function CustomStyledQRCode() { return ( @@ -203,7 +202,7 @@ function CustomStyledQRCode() { className="qr-code-custom" size={300} /> - ); + ) } ``` @@ -225,15 +224,9 @@ function CustomStyledQRCode() { ```tsx function WiFiQRCode({ ssid, password, security = 'WPA' }) { - const wifiString = `WIFI:T:${security};S:${ssid};P:${password};;`; - - return ( - - ); + const wifiString = `WIFI:T:${security};S:${ssid};P:${password};;` + + return } ``` @@ -246,15 +239,9 @@ VERSION:3.0 FN:${name} TEL:${phone} EMAIL:${email} -END:VCARD`; - - return ( - - ); +END:VCARD` + + return } ``` @@ -262,8 +249,8 @@ END:VCARD`; ```tsx function TwoFactorQRCode({ secret, issuer, accountName }) { - const otpauth = `otpauth://totp/${issuer}:${accountName}?secret=${secret}&issuer=${issuer}`; - + const otpauth = `otpauth://totp/${issuer}:${accountName}?secret=${secret}&issuer=${issuer}` + return ( - ); + ) } ``` @@ -286,22 +273,14 @@ function TwoFactorQRCode({ secret, issuer, accountName }) { ```tsx function PaymentQRCode({ amount, recipient, currency = 'USD' }) { - const paymentLink = `https://pay.example.com/?to=${recipient}&amount=${amount}¤cy=${currency}`; - - return ( - - ); + const paymentLink = `https://pay.example.com/?to=${recipient}&amount=${amount}¤cy=${currency}` + + return } ``` qrcode-demo - ## ๐Ÿงช Testing ```bash diff --git a/jest.config.js b/jest.config.js index fa8f563f..90c2946d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,13 +4,16 @@ module.exports = { roots: ['/src'], testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], transform: { - '^.+\\.(ts|tsx)$': ['ts-jest', { - tsconfig: { - jsx: 'react', - esModuleInterop: true, - allowSyntheticDefaultImports: true - } - }], + '^.+\\.(ts|tsx)$': [ + 'ts-jest', + { + tsconfig: { + jsx: 'react', + esModuleInterop: true, + allowSyntheticDefaultImports: true, + }, + }, + ], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], collectCoverageFrom: [ @@ -25,4 +28,4 @@ module.exports = { moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, -} \ No newline at end of file +} diff --git a/jest.setup.js b/jest.setup.js index c011d117..e3cab99b 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -38,4 +38,4 @@ global.Image = class { this.onload = null this.src = '' } -} \ No newline at end of file +} From e9857d93ab9c81fb92132aa0a8d99dd0f38f50e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:35:52 +0000 Subject: [PATCH 3/3] chore(deps): update actions/checkout action to v5 --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d4f0284..d2e9513f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: workflow_id: ci.yml access_token: ${{ github.token }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 30 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 704e8073..9ee74b2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: workflow_id: release.yml access_token: ${{ github.token }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 30