├── .eslintrc.cjs ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── README.zh-CN.md ├── examples ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src │ ├── App.css │ ├── App.tsx │ ├── index.css │ └── main.tsx ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── package.json ├── src ├── Marquee.tsx ├── index.ts ├── marquee.css └── types.ts ├── test ├── Marquee.test.tsx └── setup.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Test coverage 27 | coverage 28 | pnpm-lock.yaml 29 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source 2 | src 3 | examples 4 | tests 5 | 6 | # Config files 7 | .eslintrc.cjs 8 | .prettierrc 9 | tsconfig.json 10 | tsconfig.node.json 11 | vite.config.ts 12 | vitest.config.ts 13 | 14 | # Development files 15 | node_modules 16 | coverage 17 | *.log 18 | .husky 19 | .vscode 20 | .idea 21 | .DS_Store 22 | 23 | # Git files 24 | .git 25 | .gitignore 26 | 27 | # CI/CD 28 | .github -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0] - 2024-12-09 4 | 5 | ### Breaking Changes 6 | - 升级到 React 18 7 | - 迁移到 TypeScript 8 | - 使用 Vite 替代 Webpack 作为构建工具 9 | - 更新了组件 API 以支持 TypeScript 类型 10 | 11 | ### Added 12 | - 添加完整的 TypeScript 类型定义 13 | - 添加 ESM 和 CommonJS 双模块格式支持 14 | - 添加 Vitest 测试支持 15 | - 添加更好的无缝滚动支持 16 | - 添加反向滚动功能优化 17 | 18 | ### Changed 19 | - 重构为函数组件 20 | - 使用 CSS transform 替代 scroll 实现更流畅的动画 21 | - 优化滚动逻辑,提供更好的性能 22 | - 改进文档和示例 23 | 24 | ### Fixed 25 | - 修复反向滚动不连续的问题 26 | - 修复滚动重置时的视觉跳动 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Seamless Scroll 2 | 3 | English | [简体中文](./README.zh-CN.md) 4 | 5 | A modern React seamless scrolling component with perfect support for both horizontal and vertical scrolling. Built with TypeScript, featuring smooth scrolling effects, flexible configuration options, and complete type support. 6 | 7 | ## 🌐 Demo 8 | 9 | Check out the live demo: [React Seamless Scroll Demo](https://zhongs.github.io/react-marquee/) 10 | 11 | ## ✨ Features 12 | 13 | - 🔄 Perfect seamless scrolling without gaps 14 | - ↔️ Horizontal and vertical scrolling 15 | - 🎯 Customizable scroll speed 16 | - 🔁 Support for forward and reverse scrolling 17 | - ⏯️ Hover pause support 18 | - 🖱️ Click event handling 19 | - 🎨 Flexible style customization 20 | - 📱 Responsive design 21 | - 🚀 Written in TypeScript for type safety 22 | - 🔥 Built with React 18+ 23 | - 📦 Zero dependencies (except React) 24 | - ⚡️ Transform-based implementation for superior performance 25 | 26 | ## 📦 Installation 27 | 28 | ```bash 29 | npm install react-marquee-order 30 | # or 31 | pnpm add react-marquee-order 32 | # or 33 | yarn add react-marquee-order 34 | ``` 35 | 36 | ## 🚀 Usage 37 | 38 | ```tsx 39 | import React, { useRef } from 'react'; 40 | import { Marquee, MarqueeHandle } from 'react-marquee-order'; 41 | 42 | const App = () => { 43 | const marqueeRef = useRef(null); 44 | 45 | const data = [ 46 | { text: "Seamless scroll item 1" }, 47 | { text: "Seamless scroll item 2" }, 48 | { text: "Seamless scroll item 3" } 49 | ]; 50 | 51 | const handleClick = (item: any, index: number) => { 52 | console.log('Clicked:', item, 'at index:', index); 53 | }; 54 | 55 | return ( 56 |
57 | 66 | 67 | ); 68 | }; 69 | 70 | export default App; 71 | ``` 72 | 73 | ## 📖 API Reference 74 | 75 | ### Props 76 | 77 | | Prop | Type | Default | Description | 78 | |------|------|---------|-------------| 79 | | loopData | `Array` | `[]` | Data array for continuous scrolling | 80 | | direction | `'horizontal' \| 'vertical'` | `'horizontal'` | Scroll direction: horizontal/vertical | 81 | | speed | `number` | `2` | Scroll speed (pixels per frame) | 82 | | reverse | `boolean` | `false` | Whether to scroll in reverse | 83 | | hoverPause | `boolean` | `true` | Whether to pause on hover | 84 | | onClick | `(item: any, index: number) => void` | - | Callback function for item clicks | 85 | 86 | ### Methods 87 | 88 | The component exposes these methods via ref: 89 | 90 | - `play()`: Start scrolling 91 | - `pause()`: Pause scrolling 92 | - `reset()`: Reset position 93 | 94 | ## 🚨 v2.0.0 Updates 95 | 96 | - 🆙 Upgraded to React 18 97 | - 📝 Migrated to TypeScript with full type support 98 | - 🛠️ Upgraded build tool to Vite 99 | - ⚡️ Optimized scrolling algorithm for smoother experience 100 | - 🎯 Improved API design with better type support 101 | 102 | ## 🌐 Browser Support 103 | 104 | Supports all modern browsers and IE11+ with appropriate polyfills. 105 | 106 | ## 🤝 Contributing 107 | 108 | Pull requests are welcome to improve this component! 109 | 110 | ## 📄 License 111 | 112 | MIT 113 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # React 无缝滚动组件 2 | 3 | 一个现代化的 React 无缝滚动组件,完美支持横向和纵向滚动。基于 TypeScript 开发,具有丝滑流畅的滚动效果、灵活的配置选项和完整的类型支持。 4 | 5 | ## 🌐 演示 6 | 7 | 查看在线演示:[React 无缝滚动演示](https://zhongs.github.io/react-marquee/) 8 | 9 | ## ✨ 特性 10 | 11 | - 🔄 完美无缝滚动,无断层感 12 | - ↔️ 支持横向和纵向滚动 13 | - 🎯 自定义滚动速度 14 | - 🔁 支持正反向滚动 15 | - ⏯️ 支持鼠标悬停暂停 16 | - 🖱️ 支持点击事件处理 17 | - 🎨 灵活的样式定制 18 | - 📱 响应式设计 19 | - 🚀 TypeScript 编写,类型安全 20 | - 🔥 基于 React 18+ 构建 21 | - 📦 零依赖(仅依赖 React) 22 | - ⚡️ 基于 transform 实现,性能出众 23 | 24 | ## 📦 安装 25 | 26 | ```bash 27 | npm install react-marquee-order 28 | # 或者 29 | pnpm add react-marquee-order 30 | # 或者 31 | yarn add react-marquee-order 32 | ``` 33 | 34 | ## 🚀 使用示例 35 | 36 | ```tsx 37 | import React, { useRef } from 'react'; 38 | import { Marquee, MarqueeHandle } from 'react-marquee-order'; 39 | 40 | const App = () => { 41 | const marqueeRef = useRef(null); 42 | 43 | const data = [ 44 | { text: "无缝滚动第一项" }, 45 | { text: "无缝滚动第二项" }, 46 | { text: "无缝滚动第三项" } 47 | ]; 48 | 49 | const handleClick = (item: any, index: number) => { 50 | console.log('点击了:', item, '索引:', index); 51 | }; 52 | 53 | return ( 54 |
55 | 64 | 65 | ); 66 | }; 67 | 68 | export default App; 69 | ``` 70 | 71 | ## 📖 API 文档 72 | 73 | ### Props 属性 74 | 75 | | 属性 | 类型 | 默认值 | 描述 | 76 | |------|------|--------|------| 77 | | loopData | `Array` | `[]` | 要循环显示的数据数组 | 78 | | direction | `'horizontal' \| 'vertical'` | `'horizontal'` | 滚动方向:水平/垂直 | 79 | | speed | `number` | `2` | 滚动速度(每帧像素) | 80 | | reverse | `boolean` | `false` | 是否反向滚动 | 81 | | hoverPause | `boolean` | `true` | 鼠标悬停时是否暂停 | 82 | | onClick | `(item: any, index: number) => void` | - | 点击项目的回调函数 | 83 | 84 | ### 组件方法 85 | 86 | 通过 ref 可以调用以下方法: 87 | 88 | - `play()`: 开始滚动 89 | - `pause()`: 暂停滚动 90 | - `reset()`: 重置位置 91 | 92 | ## 🚨 v2.0.0 版本更新 93 | 94 | - 🆙 升级到 React 18 95 | - 📝 迁移到 TypeScript,提供完整类型支持 96 | - 🛠️ 构建工具升级到 Vite 97 | - ⚡️ 优化滚动算法,提供更流畅的体验 98 | - 🎯 改进 API 设计,提供更好的类型支持 99 | 100 | ## 🌐 浏览器支持 101 | 102 | 支持所有现代浏览器,搭配合适的 polyfills 可支持 IE11+。 103 | 104 | ## 🤝 参与贡献 105 | 106 | 欢迎提交 Pull Request 来改进这个组件! 107 | 108 | ## 📄 许可证 109 | 110 | MIT © [Your Name] 111 | 112 | --- 113 | 114 | [English](./README.md) | 简体中文 115 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Marquee Demo 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-marquee-examples", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.2.43", 17 | "@types/react-dom": "^18.2.17", 18 | "@vitejs/plugin-react": "^4.2.1", 19 | "typescript": "^5.2.2", 20 | "vite": "^5.0.8" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | react: 12 | specifier: ^18.2.0 13 | version: 18.3.1 14 | react-dom: 15 | specifier: ^18.2.0 16 | version: 18.3.1(react@18.3.1) 17 | devDependencies: 18 | '@types/react': 19 | specifier: ^18.2.43 20 | version: 18.3.14 21 | '@types/react-dom': 22 | specifier: ^18.2.17 23 | version: 18.3.2 24 | '@vitejs/plugin-react': 25 | specifier: ^4.2.1 26 | version: 4.3.4(vite@5.4.11) 27 | typescript: 28 | specifier: ^5.2.2 29 | version: 5.7.2 30 | vite: 31 | specifier: ^5.0.8 32 | version: 5.4.11 33 | 34 | packages: 35 | 36 | '@ampproject/remapping@2.3.0': 37 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 38 | engines: {node: '>=6.0.0'} 39 | 40 | '@babel/code-frame@7.26.2': 41 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 42 | engines: {node: '>=6.9.0'} 43 | 44 | '@babel/compat-data@7.26.3': 45 | resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} 46 | engines: {node: '>=6.9.0'} 47 | 48 | '@babel/core@7.26.0': 49 | resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} 50 | engines: {node: '>=6.9.0'} 51 | 52 | '@babel/generator@7.26.3': 53 | resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} 54 | engines: {node: '>=6.9.0'} 55 | 56 | '@babel/helper-compilation-targets@7.25.9': 57 | resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} 58 | engines: {node: '>=6.9.0'} 59 | 60 | '@babel/helper-module-imports@7.25.9': 61 | resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 62 | engines: {node: '>=6.9.0'} 63 | 64 | '@babel/helper-module-transforms@7.26.0': 65 | resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 66 | engines: {node: '>=6.9.0'} 67 | peerDependencies: 68 | '@babel/core': ^7.0.0 69 | 70 | '@babel/helper-plugin-utils@7.25.9': 71 | resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} 72 | engines: {node: '>=6.9.0'} 73 | 74 | '@babel/helper-string-parser@7.25.9': 75 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 76 | engines: {node: '>=6.9.0'} 77 | 78 | '@babel/helper-validator-identifier@7.25.9': 79 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 80 | engines: {node: '>=6.9.0'} 81 | 82 | '@babel/helper-validator-option@7.25.9': 83 | resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 84 | engines: {node: '>=6.9.0'} 85 | 86 | '@babel/helpers@7.26.0': 87 | resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} 88 | engines: {node: '>=6.9.0'} 89 | 90 | '@babel/parser@7.26.3': 91 | resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} 92 | engines: {node: '>=6.0.0'} 93 | hasBin: true 94 | 95 | '@babel/plugin-transform-react-jsx-self@7.25.9': 96 | resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} 97 | engines: {node: '>=6.9.0'} 98 | peerDependencies: 99 | '@babel/core': ^7.0.0-0 100 | 101 | '@babel/plugin-transform-react-jsx-source@7.25.9': 102 | resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} 103 | engines: {node: '>=6.9.0'} 104 | peerDependencies: 105 | '@babel/core': ^7.0.0-0 106 | 107 | '@babel/template@7.25.9': 108 | resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} 109 | engines: {node: '>=6.9.0'} 110 | 111 | '@babel/traverse@7.26.4': 112 | resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} 113 | engines: {node: '>=6.9.0'} 114 | 115 | '@babel/types@7.26.3': 116 | resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} 117 | engines: {node: '>=6.9.0'} 118 | 119 | '@esbuild/aix-ppc64@0.21.5': 120 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 121 | engines: {node: '>=12'} 122 | cpu: [ppc64] 123 | os: [aix] 124 | 125 | '@esbuild/android-arm64@0.21.5': 126 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 127 | engines: {node: '>=12'} 128 | cpu: [arm64] 129 | os: [android] 130 | 131 | '@esbuild/android-arm@0.21.5': 132 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 133 | engines: {node: '>=12'} 134 | cpu: [arm] 135 | os: [android] 136 | 137 | '@esbuild/android-x64@0.21.5': 138 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 139 | engines: {node: '>=12'} 140 | cpu: [x64] 141 | os: [android] 142 | 143 | '@esbuild/darwin-arm64@0.21.5': 144 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 145 | engines: {node: '>=12'} 146 | cpu: [arm64] 147 | os: [darwin] 148 | 149 | '@esbuild/darwin-x64@0.21.5': 150 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 151 | engines: {node: '>=12'} 152 | cpu: [x64] 153 | os: [darwin] 154 | 155 | '@esbuild/freebsd-arm64@0.21.5': 156 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 157 | engines: {node: '>=12'} 158 | cpu: [arm64] 159 | os: [freebsd] 160 | 161 | '@esbuild/freebsd-x64@0.21.5': 162 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 163 | engines: {node: '>=12'} 164 | cpu: [x64] 165 | os: [freebsd] 166 | 167 | '@esbuild/linux-arm64@0.21.5': 168 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 169 | engines: {node: '>=12'} 170 | cpu: [arm64] 171 | os: [linux] 172 | 173 | '@esbuild/linux-arm@0.21.5': 174 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 175 | engines: {node: '>=12'} 176 | cpu: [arm] 177 | os: [linux] 178 | 179 | '@esbuild/linux-ia32@0.21.5': 180 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 181 | engines: {node: '>=12'} 182 | cpu: [ia32] 183 | os: [linux] 184 | 185 | '@esbuild/linux-loong64@0.21.5': 186 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 187 | engines: {node: '>=12'} 188 | cpu: [loong64] 189 | os: [linux] 190 | 191 | '@esbuild/linux-mips64el@0.21.5': 192 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 193 | engines: {node: '>=12'} 194 | cpu: [mips64el] 195 | os: [linux] 196 | 197 | '@esbuild/linux-ppc64@0.21.5': 198 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 199 | engines: {node: '>=12'} 200 | cpu: [ppc64] 201 | os: [linux] 202 | 203 | '@esbuild/linux-riscv64@0.21.5': 204 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 205 | engines: {node: '>=12'} 206 | cpu: [riscv64] 207 | os: [linux] 208 | 209 | '@esbuild/linux-s390x@0.21.5': 210 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 211 | engines: {node: '>=12'} 212 | cpu: [s390x] 213 | os: [linux] 214 | 215 | '@esbuild/linux-x64@0.21.5': 216 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 217 | engines: {node: '>=12'} 218 | cpu: [x64] 219 | os: [linux] 220 | 221 | '@esbuild/netbsd-x64@0.21.5': 222 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 223 | engines: {node: '>=12'} 224 | cpu: [x64] 225 | os: [netbsd] 226 | 227 | '@esbuild/openbsd-x64@0.21.5': 228 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 229 | engines: {node: '>=12'} 230 | cpu: [x64] 231 | os: [openbsd] 232 | 233 | '@esbuild/sunos-x64@0.21.5': 234 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 235 | engines: {node: '>=12'} 236 | cpu: [x64] 237 | os: [sunos] 238 | 239 | '@esbuild/win32-arm64@0.21.5': 240 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 241 | engines: {node: '>=12'} 242 | cpu: [arm64] 243 | os: [win32] 244 | 245 | '@esbuild/win32-ia32@0.21.5': 246 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 247 | engines: {node: '>=12'} 248 | cpu: [ia32] 249 | os: [win32] 250 | 251 | '@esbuild/win32-x64@0.21.5': 252 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 253 | engines: {node: '>=12'} 254 | cpu: [x64] 255 | os: [win32] 256 | 257 | '@jridgewell/gen-mapping@0.3.5': 258 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 259 | engines: {node: '>=6.0.0'} 260 | 261 | '@jridgewell/resolve-uri@3.1.2': 262 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 263 | engines: {node: '>=6.0.0'} 264 | 265 | '@jridgewell/set-array@1.2.1': 266 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 267 | engines: {node: '>=6.0.0'} 268 | 269 | '@jridgewell/sourcemap-codec@1.5.0': 270 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 271 | 272 | '@jridgewell/trace-mapping@0.3.25': 273 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 274 | 275 | '@rollup/rollup-android-arm-eabi@4.28.1': 276 | resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} 277 | cpu: [arm] 278 | os: [android] 279 | 280 | '@rollup/rollup-android-arm64@4.28.1': 281 | resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} 282 | cpu: [arm64] 283 | os: [android] 284 | 285 | '@rollup/rollup-darwin-arm64@4.28.1': 286 | resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} 287 | cpu: [arm64] 288 | os: [darwin] 289 | 290 | '@rollup/rollup-darwin-x64@4.28.1': 291 | resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} 292 | cpu: [x64] 293 | os: [darwin] 294 | 295 | '@rollup/rollup-freebsd-arm64@4.28.1': 296 | resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} 297 | cpu: [arm64] 298 | os: [freebsd] 299 | 300 | '@rollup/rollup-freebsd-x64@4.28.1': 301 | resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} 302 | cpu: [x64] 303 | os: [freebsd] 304 | 305 | '@rollup/rollup-linux-arm-gnueabihf@4.28.1': 306 | resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} 307 | cpu: [arm] 308 | os: [linux] 309 | libc: [glibc] 310 | 311 | '@rollup/rollup-linux-arm-musleabihf@4.28.1': 312 | resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} 313 | cpu: [arm] 314 | os: [linux] 315 | libc: [musl] 316 | 317 | '@rollup/rollup-linux-arm64-gnu@4.28.1': 318 | resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} 319 | cpu: [arm64] 320 | os: [linux] 321 | libc: [glibc] 322 | 323 | '@rollup/rollup-linux-arm64-musl@4.28.1': 324 | resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} 325 | cpu: [arm64] 326 | os: [linux] 327 | libc: [musl] 328 | 329 | '@rollup/rollup-linux-loongarch64-gnu@4.28.1': 330 | resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} 331 | cpu: [loong64] 332 | os: [linux] 333 | libc: [glibc] 334 | 335 | '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': 336 | resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} 337 | cpu: [ppc64] 338 | os: [linux] 339 | libc: [glibc] 340 | 341 | '@rollup/rollup-linux-riscv64-gnu@4.28.1': 342 | resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} 343 | cpu: [riscv64] 344 | os: [linux] 345 | libc: [glibc] 346 | 347 | '@rollup/rollup-linux-s390x-gnu@4.28.1': 348 | resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} 349 | cpu: [s390x] 350 | os: [linux] 351 | libc: [glibc] 352 | 353 | '@rollup/rollup-linux-x64-gnu@4.28.1': 354 | resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} 355 | cpu: [x64] 356 | os: [linux] 357 | libc: [glibc] 358 | 359 | '@rollup/rollup-linux-x64-musl@4.28.1': 360 | resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} 361 | cpu: [x64] 362 | os: [linux] 363 | libc: [musl] 364 | 365 | '@rollup/rollup-win32-arm64-msvc@4.28.1': 366 | resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} 367 | cpu: [arm64] 368 | os: [win32] 369 | 370 | '@rollup/rollup-win32-ia32-msvc@4.28.1': 371 | resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} 372 | cpu: [ia32] 373 | os: [win32] 374 | 375 | '@rollup/rollup-win32-x64-msvc@4.28.1': 376 | resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} 377 | cpu: [x64] 378 | os: [win32] 379 | 380 | '@types/babel__core@7.20.5': 381 | resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 382 | 383 | '@types/babel__generator@7.6.8': 384 | resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} 385 | 386 | '@types/babel__template@7.4.4': 387 | resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 388 | 389 | '@types/babel__traverse@7.20.6': 390 | resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} 391 | 392 | '@types/estree@1.0.6': 393 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 394 | 395 | '@types/prop-types@15.7.14': 396 | resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} 397 | 398 | '@types/react-dom@18.3.2': 399 | resolution: {integrity: sha512-Fqp+rcvem9wEnGr3RY8dYNvSQ8PoLqjZ9HLgaPUOjJJD120uDyOxOjc/39M4Kddp9JQCxpGQbnhVQF0C0ncYVg==} 400 | 401 | '@types/react@18.3.14': 402 | resolution: {integrity: sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg==} 403 | 404 | '@vitejs/plugin-react@4.3.4': 405 | resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} 406 | engines: {node: ^14.18.0 || >=16.0.0} 407 | peerDependencies: 408 | vite: ^4.2.0 || ^5.0.0 || ^6.0.0 409 | 410 | browserslist@4.24.2: 411 | resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} 412 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 413 | hasBin: true 414 | 415 | caniuse-lite@1.0.30001687: 416 | resolution: {integrity: sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==} 417 | 418 | convert-source-map@2.0.0: 419 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 420 | 421 | csstype@3.1.3: 422 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 423 | 424 | debug@4.4.0: 425 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 426 | engines: {node: '>=6.0'} 427 | peerDependencies: 428 | supports-color: '*' 429 | peerDependenciesMeta: 430 | supports-color: 431 | optional: true 432 | 433 | electron-to-chromium@1.5.71: 434 | resolution: {integrity: sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==} 435 | 436 | esbuild@0.21.5: 437 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 438 | engines: {node: '>=12'} 439 | hasBin: true 440 | 441 | escalade@3.2.0: 442 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 443 | engines: {node: '>=6'} 444 | 445 | fsevents@2.3.3: 446 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 447 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 448 | os: [darwin] 449 | 450 | gensync@1.0.0-beta.2: 451 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 452 | engines: {node: '>=6.9.0'} 453 | 454 | globals@11.12.0: 455 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 456 | engines: {node: '>=4'} 457 | 458 | js-tokens@4.0.0: 459 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 460 | 461 | jsesc@3.0.2: 462 | resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} 463 | engines: {node: '>=6'} 464 | hasBin: true 465 | 466 | json5@2.2.3: 467 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 468 | engines: {node: '>=6'} 469 | hasBin: true 470 | 471 | loose-envify@1.4.0: 472 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 473 | hasBin: true 474 | 475 | lru-cache@5.1.1: 476 | resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 477 | 478 | ms@2.1.3: 479 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 480 | 481 | nanoid@3.3.8: 482 | resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} 483 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 484 | hasBin: true 485 | 486 | node-releases@2.0.18: 487 | resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} 488 | 489 | picocolors@1.1.1: 490 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 491 | 492 | postcss@8.4.49: 493 | resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} 494 | engines: {node: ^10 || ^12 || >=14} 495 | 496 | react-dom@18.3.1: 497 | resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} 498 | peerDependencies: 499 | react: ^18.3.1 500 | 501 | react-refresh@0.14.2: 502 | resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} 503 | engines: {node: '>=0.10.0'} 504 | 505 | react@18.3.1: 506 | resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} 507 | engines: {node: '>=0.10.0'} 508 | 509 | rollup@4.28.1: 510 | resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} 511 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 512 | hasBin: true 513 | 514 | scheduler@0.23.2: 515 | resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 516 | 517 | semver@6.3.1: 518 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 519 | hasBin: true 520 | 521 | source-map-js@1.2.1: 522 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 523 | engines: {node: '>=0.10.0'} 524 | 525 | typescript@5.7.2: 526 | resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} 527 | engines: {node: '>=14.17'} 528 | hasBin: true 529 | 530 | update-browserslist-db@1.1.1: 531 | resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} 532 | hasBin: true 533 | peerDependencies: 534 | browserslist: '>= 4.21.0' 535 | 536 | vite@5.4.11: 537 | resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} 538 | engines: {node: ^18.0.0 || >=20.0.0} 539 | hasBin: true 540 | peerDependencies: 541 | '@types/node': ^18.0.0 || >=20.0.0 542 | less: '*' 543 | lightningcss: ^1.21.0 544 | sass: '*' 545 | sass-embedded: '*' 546 | stylus: '*' 547 | sugarss: '*' 548 | terser: ^5.4.0 549 | peerDependenciesMeta: 550 | '@types/node': 551 | optional: true 552 | less: 553 | optional: true 554 | lightningcss: 555 | optional: true 556 | sass: 557 | optional: true 558 | sass-embedded: 559 | optional: true 560 | stylus: 561 | optional: true 562 | sugarss: 563 | optional: true 564 | terser: 565 | optional: true 566 | 567 | yallist@3.1.1: 568 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 569 | 570 | snapshots: 571 | 572 | '@ampproject/remapping@2.3.0': 573 | dependencies: 574 | '@jridgewell/gen-mapping': 0.3.5 575 | '@jridgewell/trace-mapping': 0.3.25 576 | 577 | '@babel/code-frame@7.26.2': 578 | dependencies: 579 | '@babel/helper-validator-identifier': 7.25.9 580 | js-tokens: 4.0.0 581 | picocolors: 1.1.1 582 | 583 | '@babel/compat-data@7.26.3': {} 584 | 585 | '@babel/core@7.26.0': 586 | dependencies: 587 | '@ampproject/remapping': 2.3.0 588 | '@babel/code-frame': 7.26.2 589 | '@babel/generator': 7.26.3 590 | '@babel/helper-compilation-targets': 7.25.9 591 | '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) 592 | '@babel/helpers': 7.26.0 593 | '@babel/parser': 7.26.3 594 | '@babel/template': 7.25.9 595 | '@babel/traverse': 7.26.4 596 | '@babel/types': 7.26.3 597 | convert-source-map: 2.0.0 598 | debug: 4.4.0 599 | gensync: 1.0.0-beta.2 600 | json5: 2.2.3 601 | semver: 6.3.1 602 | transitivePeerDependencies: 603 | - supports-color 604 | 605 | '@babel/generator@7.26.3': 606 | dependencies: 607 | '@babel/parser': 7.26.3 608 | '@babel/types': 7.26.3 609 | '@jridgewell/gen-mapping': 0.3.5 610 | '@jridgewell/trace-mapping': 0.3.25 611 | jsesc: 3.0.2 612 | 613 | '@babel/helper-compilation-targets@7.25.9': 614 | dependencies: 615 | '@babel/compat-data': 7.26.3 616 | '@babel/helper-validator-option': 7.25.9 617 | browserslist: 4.24.2 618 | lru-cache: 5.1.1 619 | semver: 6.3.1 620 | 621 | '@babel/helper-module-imports@7.25.9': 622 | dependencies: 623 | '@babel/traverse': 7.26.4 624 | '@babel/types': 7.26.3 625 | transitivePeerDependencies: 626 | - supports-color 627 | 628 | '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': 629 | dependencies: 630 | '@babel/core': 7.26.0 631 | '@babel/helper-module-imports': 7.25.9 632 | '@babel/helper-validator-identifier': 7.25.9 633 | '@babel/traverse': 7.26.4 634 | transitivePeerDependencies: 635 | - supports-color 636 | 637 | '@babel/helper-plugin-utils@7.25.9': {} 638 | 639 | '@babel/helper-string-parser@7.25.9': {} 640 | 641 | '@babel/helper-validator-identifier@7.25.9': {} 642 | 643 | '@babel/helper-validator-option@7.25.9': {} 644 | 645 | '@babel/helpers@7.26.0': 646 | dependencies: 647 | '@babel/template': 7.25.9 648 | '@babel/types': 7.26.3 649 | 650 | '@babel/parser@7.26.3': 651 | dependencies: 652 | '@babel/types': 7.26.3 653 | 654 | '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': 655 | dependencies: 656 | '@babel/core': 7.26.0 657 | '@babel/helper-plugin-utils': 7.25.9 658 | 659 | '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': 660 | dependencies: 661 | '@babel/core': 7.26.0 662 | '@babel/helper-plugin-utils': 7.25.9 663 | 664 | '@babel/template@7.25.9': 665 | dependencies: 666 | '@babel/code-frame': 7.26.2 667 | '@babel/parser': 7.26.3 668 | '@babel/types': 7.26.3 669 | 670 | '@babel/traverse@7.26.4': 671 | dependencies: 672 | '@babel/code-frame': 7.26.2 673 | '@babel/generator': 7.26.3 674 | '@babel/parser': 7.26.3 675 | '@babel/template': 7.25.9 676 | '@babel/types': 7.26.3 677 | debug: 4.4.0 678 | globals: 11.12.0 679 | transitivePeerDependencies: 680 | - supports-color 681 | 682 | '@babel/types@7.26.3': 683 | dependencies: 684 | '@babel/helper-string-parser': 7.25.9 685 | '@babel/helper-validator-identifier': 7.25.9 686 | 687 | '@esbuild/aix-ppc64@0.21.5': 688 | optional: true 689 | 690 | '@esbuild/android-arm64@0.21.5': 691 | optional: true 692 | 693 | '@esbuild/android-arm@0.21.5': 694 | optional: true 695 | 696 | '@esbuild/android-x64@0.21.5': 697 | optional: true 698 | 699 | '@esbuild/darwin-arm64@0.21.5': 700 | optional: true 701 | 702 | '@esbuild/darwin-x64@0.21.5': 703 | optional: true 704 | 705 | '@esbuild/freebsd-arm64@0.21.5': 706 | optional: true 707 | 708 | '@esbuild/freebsd-x64@0.21.5': 709 | optional: true 710 | 711 | '@esbuild/linux-arm64@0.21.5': 712 | optional: true 713 | 714 | '@esbuild/linux-arm@0.21.5': 715 | optional: true 716 | 717 | '@esbuild/linux-ia32@0.21.5': 718 | optional: true 719 | 720 | '@esbuild/linux-loong64@0.21.5': 721 | optional: true 722 | 723 | '@esbuild/linux-mips64el@0.21.5': 724 | optional: true 725 | 726 | '@esbuild/linux-ppc64@0.21.5': 727 | optional: true 728 | 729 | '@esbuild/linux-riscv64@0.21.5': 730 | optional: true 731 | 732 | '@esbuild/linux-s390x@0.21.5': 733 | optional: true 734 | 735 | '@esbuild/linux-x64@0.21.5': 736 | optional: true 737 | 738 | '@esbuild/netbsd-x64@0.21.5': 739 | optional: true 740 | 741 | '@esbuild/openbsd-x64@0.21.5': 742 | optional: true 743 | 744 | '@esbuild/sunos-x64@0.21.5': 745 | optional: true 746 | 747 | '@esbuild/win32-arm64@0.21.5': 748 | optional: true 749 | 750 | '@esbuild/win32-ia32@0.21.5': 751 | optional: true 752 | 753 | '@esbuild/win32-x64@0.21.5': 754 | optional: true 755 | 756 | '@jridgewell/gen-mapping@0.3.5': 757 | dependencies: 758 | '@jridgewell/set-array': 1.2.1 759 | '@jridgewell/sourcemap-codec': 1.5.0 760 | '@jridgewell/trace-mapping': 0.3.25 761 | 762 | '@jridgewell/resolve-uri@3.1.2': {} 763 | 764 | '@jridgewell/set-array@1.2.1': {} 765 | 766 | '@jridgewell/sourcemap-codec@1.5.0': {} 767 | 768 | '@jridgewell/trace-mapping@0.3.25': 769 | dependencies: 770 | '@jridgewell/resolve-uri': 3.1.2 771 | '@jridgewell/sourcemap-codec': 1.5.0 772 | 773 | '@rollup/rollup-android-arm-eabi@4.28.1': 774 | optional: true 775 | 776 | '@rollup/rollup-android-arm64@4.28.1': 777 | optional: true 778 | 779 | '@rollup/rollup-darwin-arm64@4.28.1': 780 | optional: true 781 | 782 | '@rollup/rollup-darwin-x64@4.28.1': 783 | optional: true 784 | 785 | '@rollup/rollup-freebsd-arm64@4.28.1': 786 | optional: true 787 | 788 | '@rollup/rollup-freebsd-x64@4.28.1': 789 | optional: true 790 | 791 | '@rollup/rollup-linux-arm-gnueabihf@4.28.1': 792 | optional: true 793 | 794 | '@rollup/rollup-linux-arm-musleabihf@4.28.1': 795 | optional: true 796 | 797 | '@rollup/rollup-linux-arm64-gnu@4.28.1': 798 | optional: true 799 | 800 | '@rollup/rollup-linux-arm64-musl@4.28.1': 801 | optional: true 802 | 803 | '@rollup/rollup-linux-loongarch64-gnu@4.28.1': 804 | optional: true 805 | 806 | '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': 807 | optional: true 808 | 809 | '@rollup/rollup-linux-riscv64-gnu@4.28.1': 810 | optional: true 811 | 812 | '@rollup/rollup-linux-s390x-gnu@4.28.1': 813 | optional: true 814 | 815 | '@rollup/rollup-linux-x64-gnu@4.28.1': 816 | optional: true 817 | 818 | '@rollup/rollup-linux-x64-musl@4.28.1': 819 | optional: true 820 | 821 | '@rollup/rollup-win32-arm64-msvc@4.28.1': 822 | optional: true 823 | 824 | '@rollup/rollup-win32-ia32-msvc@4.28.1': 825 | optional: true 826 | 827 | '@rollup/rollup-win32-x64-msvc@4.28.1': 828 | optional: true 829 | 830 | '@types/babel__core@7.20.5': 831 | dependencies: 832 | '@babel/parser': 7.26.3 833 | '@babel/types': 7.26.3 834 | '@types/babel__generator': 7.6.8 835 | '@types/babel__template': 7.4.4 836 | '@types/babel__traverse': 7.20.6 837 | 838 | '@types/babel__generator@7.6.8': 839 | dependencies: 840 | '@babel/types': 7.26.3 841 | 842 | '@types/babel__template@7.4.4': 843 | dependencies: 844 | '@babel/parser': 7.26.3 845 | '@babel/types': 7.26.3 846 | 847 | '@types/babel__traverse@7.20.6': 848 | dependencies: 849 | '@babel/types': 7.26.3 850 | 851 | '@types/estree@1.0.6': {} 852 | 853 | '@types/prop-types@15.7.14': {} 854 | 855 | '@types/react-dom@18.3.2': 856 | dependencies: 857 | '@types/react': 18.3.14 858 | 859 | '@types/react@18.3.14': 860 | dependencies: 861 | '@types/prop-types': 15.7.14 862 | csstype: 3.1.3 863 | 864 | '@vitejs/plugin-react@4.3.4(vite@5.4.11)': 865 | dependencies: 866 | '@babel/core': 7.26.0 867 | '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) 868 | '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) 869 | '@types/babel__core': 7.20.5 870 | react-refresh: 0.14.2 871 | vite: 5.4.11 872 | transitivePeerDependencies: 873 | - supports-color 874 | 875 | browserslist@4.24.2: 876 | dependencies: 877 | caniuse-lite: 1.0.30001687 878 | electron-to-chromium: 1.5.71 879 | node-releases: 2.0.18 880 | update-browserslist-db: 1.1.1(browserslist@4.24.2) 881 | 882 | caniuse-lite@1.0.30001687: {} 883 | 884 | convert-source-map@2.0.0: {} 885 | 886 | csstype@3.1.3: {} 887 | 888 | debug@4.4.0: 889 | dependencies: 890 | ms: 2.1.3 891 | 892 | electron-to-chromium@1.5.71: {} 893 | 894 | esbuild@0.21.5: 895 | optionalDependencies: 896 | '@esbuild/aix-ppc64': 0.21.5 897 | '@esbuild/android-arm': 0.21.5 898 | '@esbuild/android-arm64': 0.21.5 899 | '@esbuild/android-x64': 0.21.5 900 | '@esbuild/darwin-arm64': 0.21.5 901 | '@esbuild/darwin-x64': 0.21.5 902 | '@esbuild/freebsd-arm64': 0.21.5 903 | '@esbuild/freebsd-x64': 0.21.5 904 | '@esbuild/linux-arm': 0.21.5 905 | '@esbuild/linux-arm64': 0.21.5 906 | '@esbuild/linux-ia32': 0.21.5 907 | '@esbuild/linux-loong64': 0.21.5 908 | '@esbuild/linux-mips64el': 0.21.5 909 | '@esbuild/linux-ppc64': 0.21.5 910 | '@esbuild/linux-riscv64': 0.21.5 911 | '@esbuild/linux-s390x': 0.21.5 912 | '@esbuild/linux-x64': 0.21.5 913 | '@esbuild/netbsd-x64': 0.21.5 914 | '@esbuild/openbsd-x64': 0.21.5 915 | '@esbuild/sunos-x64': 0.21.5 916 | '@esbuild/win32-arm64': 0.21.5 917 | '@esbuild/win32-ia32': 0.21.5 918 | '@esbuild/win32-x64': 0.21.5 919 | 920 | escalade@3.2.0: {} 921 | 922 | fsevents@2.3.3: 923 | optional: true 924 | 925 | gensync@1.0.0-beta.2: {} 926 | 927 | globals@11.12.0: {} 928 | 929 | js-tokens@4.0.0: {} 930 | 931 | jsesc@3.0.2: {} 932 | 933 | json5@2.2.3: {} 934 | 935 | loose-envify@1.4.0: 936 | dependencies: 937 | js-tokens: 4.0.0 938 | 939 | lru-cache@5.1.1: 940 | dependencies: 941 | yallist: 3.1.1 942 | 943 | ms@2.1.3: {} 944 | 945 | nanoid@3.3.8: {} 946 | 947 | node-releases@2.0.18: {} 948 | 949 | picocolors@1.1.1: {} 950 | 951 | postcss@8.4.49: 952 | dependencies: 953 | nanoid: 3.3.8 954 | picocolors: 1.1.1 955 | source-map-js: 1.2.1 956 | 957 | react-dom@18.3.1(react@18.3.1): 958 | dependencies: 959 | loose-envify: 1.4.0 960 | react: 18.3.1 961 | scheduler: 0.23.2 962 | 963 | react-refresh@0.14.2: {} 964 | 965 | react@18.3.1: 966 | dependencies: 967 | loose-envify: 1.4.0 968 | 969 | rollup@4.28.1: 970 | dependencies: 971 | '@types/estree': 1.0.6 972 | optionalDependencies: 973 | '@rollup/rollup-android-arm-eabi': 4.28.1 974 | '@rollup/rollup-android-arm64': 4.28.1 975 | '@rollup/rollup-darwin-arm64': 4.28.1 976 | '@rollup/rollup-darwin-x64': 4.28.1 977 | '@rollup/rollup-freebsd-arm64': 4.28.1 978 | '@rollup/rollup-freebsd-x64': 4.28.1 979 | '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 980 | '@rollup/rollup-linux-arm-musleabihf': 4.28.1 981 | '@rollup/rollup-linux-arm64-gnu': 4.28.1 982 | '@rollup/rollup-linux-arm64-musl': 4.28.1 983 | '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 984 | '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 985 | '@rollup/rollup-linux-riscv64-gnu': 4.28.1 986 | '@rollup/rollup-linux-s390x-gnu': 4.28.1 987 | '@rollup/rollup-linux-x64-gnu': 4.28.1 988 | '@rollup/rollup-linux-x64-musl': 4.28.1 989 | '@rollup/rollup-win32-arm64-msvc': 4.28.1 990 | '@rollup/rollup-win32-ia32-msvc': 4.28.1 991 | '@rollup/rollup-win32-x64-msvc': 4.28.1 992 | fsevents: 2.3.3 993 | 994 | scheduler@0.23.2: 995 | dependencies: 996 | loose-envify: 1.4.0 997 | 998 | semver@6.3.1: {} 999 | 1000 | source-map-js@1.2.1: {} 1001 | 1002 | typescript@5.7.2: {} 1003 | 1004 | update-browserslist-db@1.1.1(browserslist@4.24.2): 1005 | dependencies: 1006 | browserslist: 4.24.2 1007 | escalade: 3.2.0 1008 | picocolors: 1.1.1 1009 | 1010 | vite@5.4.11: 1011 | dependencies: 1012 | esbuild: 0.21.5 1013 | postcss: 8.4.49 1014 | rollup: 4.28.1 1015 | optionalDependencies: 1016 | fsevents: 2.3.3 1017 | 1018 | yallist@3.1.1: {} 1019 | -------------------------------------------------------------------------------- /examples/src/App.css: -------------------------------------------------------------------------------- 1 | .demo-container { 2 | max-width: 1200px; 3 | margin: 0 auto; 4 | padding: 20px; 5 | } 6 | 7 | h1 { 8 | text-align: center; 9 | color: #333; 10 | margin-bottom: 40px; 11 | } 12 | 13 | .demo-section { 14 | margin-bottom: 40px; 15 | padding: 20px; 16 | border-radius: 8px; 17 | background: #fff; 18 | box-shadow: 0 2px 8px rgba(0,0,0,0.1); 19 | } 20 | 21 | .type-title { 22 | font-size: 18px; 23 | color: #333; 24 | margin-bottom: 20px; 25 | padding-bottom: 10px; 26 | border-bottom: 2px solid #eee; 27 | } 28 | 29 | .demo-box { 30 | display: flex; 31 | gap: 20px; 32 | margin-bottom: 20px; 33 | } 34 | 35 | .demo-box .preview { 36 | flex: 1; 37 | min-width: 0; /* 防止flex子项溢出 */ 38 | } 39 | 40 | .demo-box .code { 41 | flex: 1; 42 | min-width: 0; /* 防止flex子项溢出 */ 43 | } 44 | 45 | .box-landscape { 46 | width: 100%; 47 | height: 40px; 48 | position: relative; 49 | background: #f5f5f5; 50 | border-radius: 4px; 51 | margin-bottom: 15px; 52 | border: 1px solid #eee; 53 | overflow: hidden; 54 | } 55 | 56 | .box-vertical { 57 | width: 100%; 58 | height: 240px; 59 | position: relative; 60 | background: #f5f5f5; 61 | border-radius: 4px; 62 | margin-bottom: 15px; 63 | border: 1px solid #eee; 64 | overflow: hidden; 65 | } 66 | 67 | /* 垂直滚动样式 */ 68 | .box-vertical .marquee-container { 69 | height: 100%; 70 | } 71 | 72 | .box-vertical .marquee-content { 73 | height: 100%; 74 | } 75 | 76 | .box-vertical .marquee-group { 77 | height: 100%; 78 | margin: 0; 79 | padding: 0; 80 | gap: 0; 81 | } 82 | 83 | .box-vertical .marquee-item { 84 | height: 40px; 85 | line-height: 40px; 86 | margin: 0; 87 | padding: 0 10px; 88 | background: #fff; 89 | display: flex; 90 | align-items: center; 91 | } 92 | 93 | .controls { 94 | display: flex; 95 | gap: 10px; 96 | justify-content: center; 97 | } 98 | 99 | .button { 100 | padding: 8px 16px; 101 | border: none; 102 | border-radius: 4px; 103 | background: #1890ff; 104 | color: white; 105 | cursor: pointer; 106 | transition: background 0.3s ease; 107 | } 108 | 109 | .button:hover { 110 | background: #40a9ff; 111 | } 112 | 113 | .button:active { 114 | background: #096dd9; 115 | } 116 | 117 | /* 代码块样式 */ 118 | .code-block { 119 | margin: 0; 120 | padding: 16px; 121 | background: #f6f8fa; 122 | border-radius: 6px; 123 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 124 | font-size: 14px; 125 | line-height: 1.5; 126 | overflow: auto; 127 | border: 1px solid #eee; 128 | white-space: pre; 129 | word-wrap: normal; 130 | } 131 | 132 | .code-block code { 133 | color: #476582; 134 | } 135 | 136 | /* 响应式布局 */ 137 | @media (max-width: 768px) { 138 | .demo-container { 139 | padding: 10px; 140 | } 141 | 142 | .demo-section { 143 | padding: 15px; 144 | } 145 | 146 | .demo-box { 147 | flex-direction: column; 148 | } 149 | 150 | .type-title { 151 | font-size: 16px; 152 | } 153 | 154 | .box-landscape { 155 | height: 36px; 156 | } 157 | 158 | .box-vertical { 159 | height: 200px; 160 | } 161 | 162 | .button { 163 | padding: 6px 12px; 164 | font-size: 14px; 165 | } 166 | 167 | .code-block { 168 | font-size: 12px; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /examples/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | import { Marquee, MarqueeHandle } from '../../src/index'; 3 | 4 | const DEFAULT_SPEED = 2; 5 | 6 | const App = () => { 7 | const horizontalRef = useRef(null); 8 | const verticalRef = useRef(null); 9 | 10 | const [horizontalSpeed, setHorizontalSpeed] = useState(DEFAULT_SPEED); 11 | const [verticalSpeed, setVerticalSpeed] = useState(DEFAULT_SPEED); 12 | const [horizontalReverse, setHorizontalReverse] = useState(false); 13 | const [verticalReverse, setVerticalReverse] = useState(false); 14 | 15 | const horizontalData = [ 16 | { txt: "无缝滚动示例 1" }, 17 | { txt: "无缝滚动示例 2" }, 18 | { txt: "无缝滚动示例 3" }, 19 | { txt: "无缝滚动示例 4" }, 20 | { txt: "无缝滚动示例 5" } 21 | ]; 22 | 23 | const verticalData = [ 24 | { txt: "垂直滚动项 1" }, 25 | { txt: "垂直滚动项 2" }, 26 | { txt: "垂直滚动项 3" }, 27 | { txt: "垂直滚动项 4" }, 28 | { txt: "垂直滚动项 5" } 29 | ]; 30 | 31 | const handleClick = (item: any, index: number) => { 32 | console.log('点击了:', item, '索引:', index); 33 | alert(`点击了: ${item.txt} (索引: ${index})`); 34 | }; 35 | 36 | const handleHorizontalReset = () => { 37 | horizontalRef.current?.reset(); 38 | setHorizontalSpeed(DEFAULT_SPEED); 39 | setHorizontalReverse(false); 40 | }; 41 | 42 | const handleVerticalReset = () => { 43 | verticalRef.current?.reset(); 44 | setVerticalSpeed(DEFAULT_SPEED); 45 | setVerticalReverse(false); 46 | }; 47 | 48 | return ( 49 |
50 |

React 无缝滚动演示

51 | 52 | {/* 水平滚动演示 */} 53 |
54 |

水平滚动

55 |
56 |
57 | 58 | setHorizontalSpeed(Number(e.target.value))} 64 | /> 65 | {horizontalSpeed} 66 |
67 | 70 | 71 | 72 | 73 |
74 |
75 | 84 | 85 |

提示:鼠标悬停可暂停滚动,点击项目可触发事件

86 | 87 | 88 | {/* 垂直滚动演示 */} 89 |
90 |

垂直滚动

91 |
92 |
93 | 94 | setVerticalSpeed(Number(e.target.value))} 100 | /> 101 | {verticalSpeed} 102 |
103 | 106 | 107 | 108 | 109 |
110 |
111 | 120 | 121 |

提示:鼠标悬停可暂停滚动,点击项目可触发事件

122 | 123 | 124 | ); 125 | }; 126 | 127 | export default App; 128 | -------------------------------------------------------------------------------- /examples/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | body { 17 | margin: 0; 18 | display: flex; 19 | place-items: center; 20 | min-width: 320px; 21 | min-height: 100vh; 22 | } 23 | 24 | .container { 25 | max-width: 1200px; 26 | width: 100%; 27 | margin: 0 auto; 28 | padding: 2rem; 29 | box-sizing: border-box; 30 | } 31 | 32 | .demo-section { 33 | margin-bottom: 2rem; 34 | padding: 1rem; 35 | border: 1px solid #eee; 36 | border-radius: 8px; 37 | } 38 | 39 | .demo-section h2 { 40 | margin-top: 0; 41 | color: #333; 42 | } 43 | 44 | .controls { 45 | margin: 1rem 0; 46 | display: flex; 47 | flex-wrap: wrap; 48 | gap: 1rem; 49 | align-items: center; 50 | } 51 | 52 | button { 53 | padding: 0.5rem 1rem; 54 | border: none; 55 | border-radius: 4px; 56 | background: #646cff; 57 | color: white; 58 | cursor: pointer; 59 | transition: background-color 0.3s; 60 | font-size: 14px; 61 | white-space: nowrap; 62 | } 63 | 64 | button:hover { 65 | background: #747bff; 66 | } 67 | 68 | .speed-control { 69 | display: flex; 70 | align-items: center; 71 | gap: 0.5rem; 72 | flex-wrap: wrap; 73 | } 74 | 75 | .speed-control label { 76 | font-size: 14px; 77 | } 78 | 79 | .speed-control input[type="range"] { 80 | width: 100px; 81 | } 82 | 83 | .vertical-container { 84 | height: 200px; 85 | border: 1px solid #eee; 86 | margin: 1rem 0; 87 | } 88 | 89 | /* 移动端适配 */ 90 | @media screen and (max-width: 768px) { 91 | .container { 92 | padding: 1rem; 93 | } 94 | 95 | .demo-section { 96 | padding: 0.75rem; 97 | } 98 | 99 | .controls { 100 | gap: 0.5rem; 101 | } 102 | 103 | button { 104 | padding: 0.4rem 0.8rem; 105 | font-size: 13px; 106 | } 107 | 108 | .speed-control { 109 | width: 100%; 110 | margin-bottom: 0.5rem; 111 | } 112 | 113 | .speed-control input[type="range"] { 114 | flex: 1; 115 | min-width: 120px; 116 | } 117 | 118 | h1 { 119 | font-size: 1.5rem; 120 | margin: 1rem 0; 121 | } 122 | 123 | h2 { 124 | font-size: 1.2rem; 125 | } 126 | 127 | p { 128 | font-size: 0.9rem; 129 | } 130 | } 131 | 132 | /* 小屏幕手机适配 */ 133 | @media screen and (max-width: 480px) { 134 | .container { 135 | padding: 0.75rem; 136 | } 137 | 138 | .demo-section { 139 | padding: 0.5rem; 140 | margin-bottom: 1rem; 141 | } 142 | 143 | button { 144 | padding: 0.3rem 0.6rem; 145 | font-size: 12px; 146 | } 147 | 148 | .vertical-container { 149 | height: 150px; 150 | } 151 | } 152 | 153 | @media (prefers-color-scheme: light) { 154 | :root { 155 | color: #213547; 156 | background-color: #ffffff; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | import '../../src/marquee.css' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | , 11 | ) 12 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: '/react-marquee/', 8 | server: { 9 | port: 3000, 10 | }, 11 | build: { 12 | outDir: 'dist', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-marquee-order", 3 | "version": "2.0.0", 4 | "description": "A modern React seamless scroll component with TypeScript support", 5 | "type": "module", 6 | "main": "./dist/react-marquee.umd.js", 7 | "module": "./dist/react-marquee.es.js", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/react-marquee.es.js", 12 | "require": "./dist/react-marquee.umd.js", 13 | "types": "./dist/index.d.ts" 14 | }, 15 | "./dist/style.css": "./dist/style.css" 16 | }, 17 | "files": [ 18 | "dist", 19 | "README.md", 20 | "CHANGELOG.md" 21 | ], 22 | "scripts": { 23 | "build": "tsc && vite build", 24 | "examples:dev": "cd examples && pnpm dev", 25 | "examples:build": "cd examples && pnpm i && pnpm build", 26 | "test": "vitest run", 27 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 28 | "format": "prettier --write \"src/**/*.{ts,tsx}\"", 29 | "prepare": "husky install && pnpm build", 30 | "deploy": "npm run examples:build && gh-pages -d examples/dist" 31 | }, 32 | "keywords": [ 33 | "react", 34 | "scroll", 35 | "seamless", 36 | "marquee", 37 | "typescript", 38 | "animation", 39 | "component", 40 | "ui" 41 | ], 42 | "peerDependencies": { 43 | "react": ">=18.0.0", 44 | "react-dom": ">=18.0.0" 45 | }, 46 | "devDependencies": { 47 | "@testing-library/jest-dom": "^6.1.0", 48 | "@testing-library/react": "^14.0.0", 49 | "@types/react": "^18.2.0", 50 | "@types/react-dom": "^18.2.0", 51 | "@typescript-eslint/eslint-plugin": "^6.0.0", 52 | "@typescript-eslint/parser": "^6.0.0", 53 | "@vitejs/plugin-react": "^4.0.0", 54 | "eslint": "^8.0.0", 55 | "eslint-plugin-react-hooks": "^4.6.0", 56 | "eslint-plugin-react-refresh": "^0.4.5", 57 | "gh-pages": "^6.2.0", 58 | "husky": "^8.0.3", 59 | "jsdom": "^22.0.0", 60 | "prettier": "^3.1.0", 61 | "typescript": "^5.0.0", 62 | "vite": "^5.0.0", 63 | "vite-plugin-dts": "^3.0.0", 64 | "vitest": "^1.0.0" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "git+https://github.com/zhongs/react-marquee.git" 69 | }, 70 | "author": "Your Name", 71 | "license": "MIT", 72 | "publishConfig": { 73 | "access": "public" 74 | }, 75 | "engines": { 76 | "node": ">=16" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Marquee.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from 'react'; 2 | import { MarqueeProps, MarqueeItem } from './types'; 3 | import './marquee.css'; 4 | 5 | export interface MarqueeHandle { 6 | play: () => void; 7 | pause: () => void; 8 | reset: () => void; 9 | } 10 | 11 | // 计算建议的滚动速度 12 | const calculateSpeed = (content: MarqueeItem[], direction: 'horizontal' | 'vertical'): number => { 13 | // 计算内容总长度 14 | const totalLength = content.reduce((acc, item) => acc + (item.txt?.length || 0), 0); 15 | 16 | // 基础速度参数 17 | const baseSpeed = 2; 18 | 19 | // 根据内容长度调整速度 20 | // 水平方向:内容越长,速度适当增加 21 | // 垂直方向:保持相对稳定的速度 22 | if (direction === 'horizontal') { 23 | // 每20个字符增加1的速度,但设置上限为8 24 | const adjustedSpeed = baseSpeed + Math.min(Math.floor(totalLength / 20), 6); 25 | return adjustedSpeed; 26 | } else { 27 | // 垂直方向速度相对稳定,仅做小幅调整 28 | return baseSpeed + Math.min(Math.floor(totalLength / 50), 2); 29 | } 30 | }; 31 | 32 | export const Marquee = forwardRef(({ 33 | loopData, 34 | direction = 'horizontal', 35 | speed, 36 | verticalItemHeight = '60px', 37 | reverse = false, 38 | hoverPause = false, 39 | autoPlay = true, 40 | onClick, 41 | className = '', 42 | style = {}, 43 | }, ref) => { 44 | const containerRef = useRef(null); 45 | const contentRef = useRef(null); 46 | const [isRunning, setIsRunning] = useState(autoPlay); 47 | const [isPaused, setIsPaused] = useState(false); 48 | const animationRef = useRef(); 49 | const [offset, setOffset] = useState(0); 50 | 51 | // 如果没有提供速度,则自动计算 52 | const effectiveSpeed = speed ?? calculateSpeed(loopData, direction); 53 | 54 | useImperativeHandle(ref, () => ({ 55 | play: () => { 56 | setIsRunning(true); 57 | setIsPaused(false); 58 | }, 59 | pause: () => { 60 | setIsPaused(true); 61 | }, 62 | reset: () => { 63 | setOffset(0); 64 | if (contentRef.current) { 65 | contentRef.current.style.transform = 'translate(0, 0)'; 66 | } 67 | setIsRunning(true); 68 | setIsPaused(false); 69 | } 70 | })); 71 | 72 | const resetPosition = useCallback(() => { 73 | if (!containerRef.current || !contentRef.current) return; 74 | 75 | const content = contentRef.current; 76 | const isHorizontal = direction === 'horizontal'; 77 | 78 | // 获取单个内容的尺寸 79 | const itemSize = isHorizontal ? 80 | content.scrollWidth / 3 : // 因为我们复制了两份内容,所以总宽度除以3 81 | content.scrollHeight / 3; 82 | 83 | // 设置初始位置 84 | setOffset(itemSize); 85 | }, [direction]); 86 | 87 | const animate = useCallback(() => { 88 | if (!containerRef.current || !contentRef.current || !isRunning || isPaused) return; 89 | 90 | const content = contentRef.current; 91 | const isHorizontal = direction === 'horizontal'; 92 | 93 | // 获取单个内容的尺寸 94 | const itemSize = isHorizontal ? 95 | content.scrollWidth / 3 : 96 | content.scrollHeight / 3; 97 | 98 | let newOffset = offset + (reverse ? -effectiveSpeed : effectiveSpeed); 99 | 100 | // 当偏移量超出范围时重置位置 101 | if (!reverse) { 102 | if (newOffset >= itemSize * 2) { 103 | newOffset = itemSize; 104 | } 105 | } else { 106 | if (newOffset <= 0) { 107 | newOffset = itemSize; 108 | } 109 | } 110 | 111 | setOffset(newOffset); 112 | 113 | // 应用偏移量 114 | if (isHorizontal) { 115 | content.style.transform = `translateX(${-newOffset}px)`; 116 | } else { 117 | content.style.transform = `translateY(${-newOffset}px)`; 118 | } 119 | 120 | animationRef.current = requestAnimationFrame(animate); 121 | }, [isRunning, isPaused, direction, reverse, effectiveSpeed, offset]); 122 | 123 | useEffect(() => { 124 | resetPosition(); 125 | }, [direction, resetPosition]); 126 | 127 | useEffect(() => { 128 | if (isRunning && !isPaused) { 129 | animationRef.current = requestAnimationFrame(animate); 130 | } 131 | return () => { 132 | if (animationRef.current) { 133 | cancelAnimationFrame(animationRef.current); 134 | } 135 | }; 136 | }, [isRunning, isPaused, animate]); 137 | 138 | const handleMouseEnter = () => { 139 | if (hoverPause) { 140 | setIsPaused(true); 141 | } 142 | }; 143 | 144 | const handleMouseLeave = () => { 145 | if (hoverPause) { 146 | setIsPaused(false); 147 | } 148 | }; 149 | 150 | const handleClick = (item: MarqueeItem, index: number) => { 151 | if (onClick) { 152 | onClick(item, index); 153 | } 154 | }; 155 | 156 | return ( 157 |
164 |
179 | {/* 第一份内容 */} 180 |
181 | {loopData.map((item, index) => ( 182 |
handleClick(item, index)} 190 | > 191 | {item.txt} 192 |
193 | ))} 194 |
195 | {/* 第二份内容(中间) */} 196 |
197 | {loopData.map((item, index) => ( 198 |
handleClick(item, index)} 206 | > 207 | {item.txt} 208 |
209 | ))} 210 |
211 | {/* 第三份内容 */} 212 |
213 | {loopData.map((item, index) => ( 214 |
handleClick(item, index)} 222 | > 223 | {item.txt} 224 |
225 | ))} 226 |
227 |
228 |
229 | ); 230 | }); 231 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Marquee } from './Marquee'; 2 | export type { MarqueeProps, MarqueeItem } from './types'; 3 | export type { MarqueeHandle } from './Marquee'; 4 | -------------------------------------------------------------------------------- /src/marquee.css: -------------------------------------------------------------------------------- 1 | .marquee-container { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | overflow: hidden; 6 | } 7 | 8 | .marquee-container.horizontal { 9 | overflow-x: hidden; 10 | overflow-y: hidden; 11 | height: 40px; 12 | } 13 | 14 | .marquee-container.vertical { 15 | overflow-x: hidden; 16 | overflow-y: hidden; 17 | } 18 | 19 | .marquee-content { 20 | display: flex; 21 | white-space: nowrap; 22 | will-change: transform; 23 | height: 100%; 24 | } 25 | 26 | .horizontal .marquee-content { 27 | flex-direction: row; 28 | width: max-content; 29 | height: 100%; 30 | } 31 | 32 | .vertical .marquee-content { 33 | flex-direction: column; 34 | min-height: 100%; 35 | margin: 0; 36 | padding: 0; 37 | gap: 0; 38 | } 39 | 40 | .marquee-group { 41 | display: flex; 42 | flex-shrink: 0; 43 | margin: 0; 44 | padding: 0; 45 | gap: 0; 46 | } 47 | 48 | .horizontal .marquee-group { 49 | flex-direction: row; 50 | height: 100%; 51 | } 52 | 53 | .vertical .marquee-group { 54 | flex-direction: column; 55 | width: 100%; 56 | margin: 0; 57 | padding: 0; 58 | gap: 0; 59 | } 60 | 61 | .marquee-item { 62 | display: flex; 63 | align-items: center; 64 | cursor: pointer; 65 | transition: background-color 0.3s; 66 | user-select: none; 67 | box-sizing: border-box; 68 | margin: 0; 69 | padding: 0; 70 | } 71 | 72 | .horizontal .marquee-item { 73 | height: 100%; 74 | padding: 0 20px; 75 | } 76 | 77 | .vertical .marquee-item { 78 | width: 100%; 79 | height: 40px; 80 | line-height: 40px; 81 | padding: 0 10px; 82 | margin: 0; 83 | justify-content: center; 84 | border-bottom: 1px solid #eee; 85 | } 86 | 87 | .marquee-item:hover { 88 | background-color: rgba(0, 0, 0, 0.05); 89 | } 90 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MarqueeItem { 2 | txt: string; 3 | [key: string]: any; 4 | } 5 | 6 | export interface MarqueeProps { 7 | /** 8 | * 循环显示的数据数组 9 | */ 10 | loopData: MarqueeItem[]; 11 | 12 | /** 13 | * 滚动方向,可选 'horizontal' 或 'vertical' 14 | * @default 'horizontal' 15 | */ 16 | direction?: 'horizontal' | 'vertical'; 17 | 18 | /** 19 | * 滚动速度,可选。如果不提供,将根据内容长度自动计算合适的速度。 20 | * 水平方向:内容越长,速度适当增加(每20个字符增加1的速度,上限为8) 21 | * 垂直方向:保持相对稳定的速度(仅根据内容长度做小幅调整) 22 | */ 23 | speed?: number; 24 | 25 | /** 26 | * 垂直滚动时的单项高度 27 | * @default '60px' 28 | */ 29 | verticalItemHeight?: string; 30 | 31 | /** 32 | * 是否反向滚动 33 | * @default false 34 | */ 35 | reverse?: boolean; 36 | 37 | /** 38 | * 鼠标悬停时是否暂停 39 | * @default false 40 | */ 41 | hoverPause?: boolean; 42 | 43 | /** 44 | * 是否自动播放 45 | * @default true 46 | */ 47 | autoPlay?: boolean; 48 | 49 | /** 50 | * 点击事件回调 51 | */ 52 | onClick?: (item: MarqueeItem, index: number) => void; 53 | 54 | /** 55 | * 自定义类名 56 | */ 57 | className?: string; 58 | 59 | /** 60 | * 自定义样式 61 | */ 62 | style?: React.CSSProperties; 63 | } 64 | -------------------------------------------------------------------------------- /test/Marquee.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi } from 'vitest'; 2 | import { render, screen, fireEvent } from '@testing-library/react'; 3 | import { Marquee } from '../src/Marquee'; 4 | import React from 'react'; 5 | 6 | describe('Marquee Component', () => { 7 | const mockData = [ 8 | { text: 'Item 1' }, 9 | { text: 'Item 2' }, 10 | { text: 'Item 3' } 11 | ]; 12 | 13 | beforeEach(() => { 14 | vi.useFakeTimers(); 15 | }); 16 | 17 | it('renders without crashing', () => { 18 | render(); 19 | expect(screen.getByText('Item 1')).toBeDefined(); 20 | }); 21 | 22 | it('renders all items', () => { 23 | render(); 24 | mockData.forEach(item => { 25 | expect(screen.getByText(item.text)).toBeDefined(); 26 | }); 27 | }); 28 | 29 | it('handles click events', () => { 30 | const handleClick = vi.fn(); 31 | render(); 32 | 33 | fireEvent.click(screen.getByText('Item 1')); 34 | expect(handleClick).toHaveBeenCalledWith(mockData[0], 0); 35 | }); 36 | 37 | it('pauses on hover when hoverPause is true', () => { 38 | render(); 39 | const container = screen.getByText('Item 1').parentElement; 40 | 41 | expect(container).toBeDefined(); 42 | if (container) { 43 | fireEvent.mouseEnter(container); 44 | // 验证暂停状态 45 | // 这里可以添加更多具体的暂停状态检查 46 | } 47 | }); 48 | 49 | it('supports both horizontal and vertical directions', () => { 50 | const { rerender } = render(); 51 | expect(screen.getByText('Item 1').parentElement?.className).toContain('horizontal'); 52 | 53 | rerender(); 54 | expect(screen.getByText('Item 1').parentElement?.className).toContain('vertical'); 55 | }); 56 | 57 | it('supports reverse scrolling', () => { 58 | const { rerender } = render(); 59 | const container = screen.getByText('Item 1').parentElement; 60 | 61 | expect(container).toBeDefined(); 62 | if (container) { 63 | const initialTransform = container.style.transform; 64 | 65 | rerender(); 66 | // 验证滚动方向改变 67 | // 这里可以添加更多具体的方向检查 68 | } 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react-jsx", 14 | "strict": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "declaration": true, 19 | "declarationDir": "dist", 20 | "emitDeclarationOnly": true 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import dts from 'vite-plugin-dts' 4 | import { resolve } from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig(({ command, mode }) => { 8 | const config = { 9 | plugins: [ 10 | react(), 11 | dts({ 12 | insertTypesEntry: true, 13 | }), 14 | ], 15 | } 16 | 17 | if (mode === 'demo') { 18 | return { 19 | ...config, 20 | root: 'examples', 21 | } 22 | } 23 | 24 | if (command === 'build') { 25 | return { 26 | ...config, 27 | build: { 28 | lib: { 29 | entry: resolve(__dirname, 'src/index.ts'), 30 | name: 'ReactMarquee', 31 | formats: ['es', 'umd'], 32 | fileName: (format) => `react-marquee.${format}.js`, 33 | }, 34 | rollupOptions: { 35 | external: ['react', 'react-dom'], 36 | output: { 37 | globals: { 38 | react: 'React', 39 | 'react-dom': 'ReactDOM', 40 | }, 41 | }, 42 | }, 43 | }, 44 | } 45 | } 46 | 47 | return config 48 | }) 49 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: 'jsdom', 8 | setupFiles: ['./test/setup.ts'], 9 | globals: true, 10 | include: ['./test/**/*.{test,spec}.{ts,tsx}'] 11 | }, 12 | }) 13 | --------------------------------------------------------------------------------