diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..8ae6f3e
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,28 @@
+{
+ "root": true,
+ "env": {
+ "browser": true,
+ "es2020": true,
+ "node": true,
+ "jest": true
+ },
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 2020,
+ "sourceType": "module"
+ },
+ "plugins": ["@typescript-eslint"],
+ "extends": ["eslint:recommended"],
+ "rules": {
+ "no-console": "warn",
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
+ "no-undef": "off",
+ "no-empty": "off",
+ "no-useless-escape": "off",
+ "no-control-regex": "off",
+ "no-regex-spaces": "off",
+ "no-constant-condition": ["warn", { "checkLoops": false }]
+ },
+ "ignorePatterns": ["dist/", "coverage/", "node_modules/"]
+}
diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
new file mode 100644
index 0000000..26dd42c
--- /dev/null
+++ b/.gitea/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ test:
+ runs-on: debian-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x, 20.x, 22.x, 24.x]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Type check
+ run: npm run typecheck
+
+ - name: Lint
+ run: npm run lint
+ continue-on-error: true
+
+ - name: Run tests
+ run: npm test
+
+ - name: Build
+ run: npm run build
diff --git a/.gitignore b/.gitignore
index b96bc23..f08e9e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,7 @@ node_modules/
dist/
coverage/
.DS_Store
+.zcode/
+
+# 本地 npm 发布凭据(含 _auth,勿提交)
.npmrc
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1d60028
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,47 @@
+# Changelog
+
+All notable changes to MetonaToast will be documented in this file.
+
+## [0.2.0] - 2026-07-25
+
+### Changed
+- **TypeScript 源码重构**:全部源码从 JavaScript 迁移到 TypeScript
+- 删除手动维护的 `types/index.d.ts`,类型由源码自动生成
+- `core.js` (1244行) 拆分为 `toast.ts` + `api.ts` + `templates.ts`
+- 构建工具链:Babel → `ts-jest`,新增 `@rollup/plugin-typescript`
+- 测试迁移至 TypeScript (`tests/index.test.ts`)
+
+### Added
+- `tsconfig.json` (strict: true)
+- `src/types.ts` 核心类型模块
+- `.eslintrc.json` ESLint 配置
+- `.gitea/workflows/ci.yml` CI 工作流
+- `CHANGELOG.md`
+
+### Removed
+- `babel.config.js`
+- `types/` 目录
+
+## [0.1.2] - 2026-07-24
+
+### Fixed
+- 补全和修正 TypeScript 类型声明
+- 全面修复、优化与增强
+
+## [0.1.1] - 2026-07-23
+
+### Changed
+- 版本号重置,更新文档页面
+
+## [0.1.0] - 2026-07-22
+
+### Added
+- 初始发布
+- 107 种内置图标
+- 11 种动画效果
+- 4 种主题(light/dark/auto/warm)
+- 国际化支持 (zh-CN/en-US)
+- 插件系统 (keyboard/persistence/accessibility)
+- 拖拽关闭、进度条、倒计时等高级特性
+- TypeScript 类型声明
+- 零依赖
diff --git a/README.md b/README.md
index c601a87..a43c481 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,14 @@
## 特性
-- **零依赖** — 纯原生 JavaScript,gzip 后不到 10KB
+- **零依赖** — TypeScript 严格模式源码,gzip 后不到 10KB
- **107 种图标类型** — success/error/warning/info/loading 及更多扩展类型,覆盖所有常见场景
- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,每种效果明显不同
- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题
- **国际化** — 内置 zh-CN / en-US 完整翻译(去重优化),可通过 `addTranslations()` 扩展
- **插件系统** — 3 款预设插件 (keyboard/persistence/accessibility) + 自定义插件
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭
-- **TypeScript** — 完整类型定义,已对齐实际实现无 phantom 方法
+- **TypeScript** — 严格模式,源码级类型安全,类型从源码自动生成无手动维护
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入监控系统
## 安装
@@ -58,7 +58,7 @@ const MeToast = require('@metona-team/metona-toast');
| Opera | 51+ |
| iOS Safari | 11+ |
| Android Chrome | 64+ |
-| Node.js | 18+(ESM & CJS 双格式) |
+| Node.js | 16+(ESM & CJS 双格式) |
| TypeScript | 4.5+(完整类型声明) |
核心依赖:Web Animations API(Chrome 64+, Firefox 63+, Safari 11+)、Pointer Events、CSS Grid、CSS Custom Properties。
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index 3483a00..0000000
--- a/babel.config.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = {
- presets: [
- [
- '@babel/preset-env',
- {
- targets: {
- node: 'current',
- },
- modules: 'commonjs',
- },
- ],
- ],
- plugins: [
- '@babel/plugin-transform-modules-commonjs',
- ],
-};
diff --git a/build.sh b/build.sh
index 273850c..9d16350 100644
--- a/build.sh
+++ b/build.sh
@@ -1,83 +1,25 @@
#!/bin/bash
-# MetonaToast 构建脚本
+# 构建脚本 — 安装依赖 → 测试 → 构建
-echo "🍞 MetonaToast 构建脚本"
-echo "========================"
+echo "🔨 MetonaToast 构建脚本"
-# 检查Node.js
if ! command -v node &> /dev/null; then
- echo "❌ 错误: 未找到Node.js"
+ echo "❌ 未找到 Node.js"
exit 1
fi
-# 检查npm
-if ! command -v npm &> /dev/null; then
- echo "❌ 错误: 未找到npm"
- exit 1
-fi
+echo "✅ Node.js: $(node -v)"
-echo "✅ Node.js版本: $(node -v)"
-echo "✅ npm版本: $(npm -v)"
-
-# 安装依赖
-echo ""
echo "📦 安装依赖..."
-npm install
+npm install || exit 1
-if [ $? -ne 0 ]; then
- echo "❌ 依赖安装失败"
- exit 1
-fi
-
-echo "✅ 依赖安装完成"
-
-# 运行测试
-echo ""
echo "🧪 运行测试..."
-npm test
+npm test || exit 1
-if [ $? -ne 0 ]; then
- echo "❌ 测试失败"
- exit 1
-fi
+echo "🔨 构建..."
+npm run build || exit 1
-echo "✅ 测试通过"
-
-# 构建项目
echo ""
-echo "🔨 构建项目..."
-npm run build
-
-if [ $? -ne 0 ]; then
- echo "❌ 构建失败"
- exit 1
-fi
-
echo "✅ 构建完成"
-
-# 显示构建结果
-echo ""
-echo "📊 构建结果:"
-echo "========================"
ls -lh dist/
-
-echo ""
-echo "✅ 构建成功完成!"
-echo ""
-echo "📁 文件结构:"
-echo " - dist/metona-toast.js (UMD格式)"
-echo " - dist/metona-toast.esm.js (ES Module格式)"
-echo " - dist/metona-toast.cjs.js (CommonJS格式)"
-echo " - dist/metona-toast.min.js (压缩版本)"
-echo " - dist/metona-toast.d.ts (TypeScript声明)"
-echo ""
-echo "🚀 使用方法:"
-echo " 1. 浏览器: "
-echo " 2. ES Module: import MeToast from 'dist/metona-toast.esm.js'"
-echo " 3. CommonJS: const MeToast = require('dist/metona-toast.cjs.js')"
-echo ""
-echo "📝 示例:"
-echo " 打开 site/index.html 查看官网"
-echo " 打开 site/demo.html 查看演示"
-echo ""
diff --git a/jest.config.cjs b/jest.config.cjs
new file mode 100644
index 0000000..86f37ab
--- /dev/null
+++ b/jest.config.cjs
@@ -0,0 +1,34 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'jsdom',
+ transform: {
+ '^.+\\.ts$': ['ts-jest', {
+ tsconfig: {
+ target: 'ES2020',
+ module: 'ESNext',
+ moduleResolution: 'node',
+ esModuleInterop: true,
+ strict: true,
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
+ allowImportingTsExtensions: true,
+ noEmit: true,
+ },
+ }],
+ },
+ moduleNameMapper: {
+ '^(\\.{1,2}/.*)\\.js$': '$1',
+ },
+ transformIgnorePatterns: [
+ '/node_modules/(?!(@rollup)/)',
+ ],
+ moduleFileExtensions: ['ts', 'js', 'json'],
+ collectCoverageFrom: [
+ 'src/**/*.ts',
+ '!src/index.ts',
+ '!src/types.ts',
+ ],
+ coverageDirectory: 'coverage',
+ coverageReporters: ['text', 'lcov'],
+ verbose: true,
+};
diff --git a/jest.config.js b/jest.config.js
deleted file mode 100644
index 1f60f98..0000000
--- a/jest.config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-module.exports = {
- testEnvironment: 'jsdom',
- transform: {
- '^.+\\.js$': 'babel-jest',
- },
- transformIgnorePatterns: [
- '/node_modules/(?!(@rollup)/)',
- ],
- moduleFileExtensions: ['js', 'json'],
- collectCoverageFrom: [
- 'src/**/*.js',
- '!src/index.js',
- ],
- coverageDirectory: 'coverage',
- coverageReporters: ['text', 'lcov'],
- verbose: true,
-};
diff --git a/package-lock.json b/package-lock.json
index 56587f7..9a83ad4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,22 +1,21 @@
{
- "name": "metona-toast",
- "version": "2.0.0",
+ "name": "@metona-team/metona-toast",
+ "version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "metona-toast",
- "version": "2.0.0",
+ "name": "@metona-team/metona-toast",
+ "version": "0.2.0",
"license": "MIT",
"devDependencies": {
- "@babel/core": "^7.22.0",
- "@babel/plugin-transform-modules-commonjs": "^7.22.0",
- "@babel/preset-env": "^7.22.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-terser": "^0.4.0",
+ "@rollup/plugin-typescript": "^11.1.6",
"@types/jest": "^29.5.0",
- "babel-jest": "^29.5.0",
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
+ "@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.40.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
@@ -25,10 +24,12 @@
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-serve": "^2.0.1",
+ "ts-jest": "^29.4.12",
+ "tslib": "^2.8.1",
"typescript": "^5.0.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -104,19 +105,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
- "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-compilation-targets": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
@@ -134,63 +122,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
- "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.29.7",
- "@babel/helper-member-expression-to-functions": "^7.29.7",
- "@babel/helper-optimise-call-expression": "^7.29.7",
- "@babel/helper-replace-supers": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
- "@babel/traverse": "^7.29.7",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz",
- "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.29.7",
- "regexpu-core": "^6.3.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.8",
- "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz",
- "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-plugin-utils": "^7.28.6",
- "debug": "^4.4.3",
- "lodash.debounce": "^4.0.8",
- "resolve": "^1.22.11"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
"node_modules/@babel/helper-globals": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
@@ -201,20 +132,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
- "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-module-imports": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
@@ -247,19 +164,6 @@
"@babel/core": "^7.0.0"
}
},
- "node_modules/@babel/helper-optimise-call-expression": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
- "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-plugin-utils": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
@@ -270,56 +174,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-remap-async-to-generator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz",
- "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.29.7",
- "@babel/helper-wrap-function": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-replace-supers": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
- "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-member-expression-to-functions": "^7.29.7",
- "@babel/helper-optimise-call-expression": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
- "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-string-parser": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
@@ -350,21 +204,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-wrap-function": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz",
- "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.29.7",
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helpers": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz",
@@ -395,120 +234,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz",
- "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz",
- "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz",
- "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz",
- "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz",
- "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
- "@babel/plugin-transform-optional-chaining": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.13.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz",
- "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0-placeholder-for-preset-env.2",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
- "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
"resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
@@ -564,22 +289,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-import-assertions": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz",
- "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz",
@@ -764,980 +473,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
- "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-arrow-functions": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
- "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-async-generator-functions": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz",
- "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-remap-async-to-generator": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-async-to-generator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz",
- "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-remap-async-to-generator": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoped-functions": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz",
- "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz",
- "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-class-properties": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz",
- "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-class-static-block": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz",
- "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.12.0"
- }
- },
- "node_modules/@babel/plugin-transform-classes": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz",
- "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.29.7",
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-globals": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-replace-supers": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-computed-properties": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz",
- "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/template": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz",
- "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-dotall-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz",
- "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-keys": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz",
- "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz",
- "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-dynamic-import": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz",
- "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-explicit-resource-management": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz",
- "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/plugin-transform-destructuring": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz",
- "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-export-namespace-from": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz",
- "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-for-of": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz",
- "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-function-name": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz",
- "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-json-strings": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz",
- "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-literals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz",
- "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz",
- "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-member-expression-literals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz",
- "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-amd": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz",
- "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz",
- "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz",
- "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-umd": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz",
- "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz",
- "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-new-target": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz",
- "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz",
- "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-numeric-separator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz",
- "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz",
- "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/plugin-transform-destructuring": "^7.29.7",
- "@babel/plugin-transform-parameters": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-object-super": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz",
- "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-replace-supers": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-catch-binding": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz",
- "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz",
- "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-parameters": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz",
- "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-private-methods": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz",
- "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz",
- "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.29.7",
- "@babel/helper-create-class-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-property-literals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz",
- "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-regenerator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz",
- "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-regexp-modifiers": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz",
- "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-reserved-words": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz",
- "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-shorthand-properties": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
- "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-spread": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz",
- "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-sticky-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz",
- "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-template-literals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
- "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-typeof-symbol": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz",
- "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-escapes": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz",
- "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-property-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz",
- "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz",
- "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-sets-regex": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz",
- "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/preset-env": {
- "version": "7.29.7",
- "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.29.7.tgz",
- "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.29.7",
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-plugin-utils": "^7.29.7",
- "@babel/helper-validator-option": "^7.29.7",
- "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7",
- "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7",
- "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7",
- "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7",
- "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
- "@babel/plugin-syntax-import-assertions": "^7.29.7",
- "@babel/plugin-syntax-import-attributes": "^7.29.7",
- "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
- "@babel/plugin-transform-arrow-functions": "^7.29.7",
- "@babel/plugin-transform-async-generator-functions": "^7.29.7",
- "@babel/plugin-transform-async-to-generator": "^7.29.7",
- "@babel/plugin-transform-block-scoped-functions": "^7.29.7",
- "@babel/plugin-transform-block-scoping": "^7.29.7",
- "@babel/plugin-transform-class-properties": "^7.29.7",
- "@babel/plugin-transform-class-static-block": "^7.29.7",
- "@babel/plugin-transform-classes": "^7.29.7",
- "@babel/plugin-transform-computed-properties": "^7.29.7",
- "@babel/plugin-transform-destructuring": "^7.29.7",
- "@babel/plugin-transform-dotall-regex": "^7.29.7",
- "@babel/plugin-transform-duplicate-keys": "^7.29.7",
- "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7",
- "@babel/plugin-transform-dynamic-import": "^7.29.7",
- "@babel/plugin-transform-explicit-resource-management": "^7.29.7",
- "@babel/plugin-transform-exponentiation-operator": "^7.29.7",
- "@babel/plugin-transform-export-namespace-from": "^7.29.7",
- "@babel/plugin-transform-for-of": "^7.29.7",
- "@babel/plugin-transform-function-name": "^7.29.7",
- "@babel/plugin-transform-json-strings": "^7.29.7",
- "@babel/plugin-transform-literals": "^7.29.7",
- "@babel/plugin-transform-logical-assignment-operators": "^7.29.7",
- "@babel/plugin-transform-member-expression-literals": "^7.29.7",
- "@babel/plugin-transform-modules-amd": "^7.29.7",
- "@babel/plugin-transform-modules-commonjs": "^7.29.7",
- "@babel/plugin-transform-modules-systemjs": "^7.29.7",
- "@babel/plugin-transform-modules-umd": "^7.29.7",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7",
- "@babel/plugin-transform-new-target": "^7.29.7",
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7",
- "@babel/plugin-transform-numeric-separator": "^7.29.7",
- "@babel/plugin-transform-object-rest-spread": "^7.29.7",
- "@babel/plugin-transform-object-super": "^7.29.7",
- "@babel/plugin-transform-optional-catch-binding": "^7.29.7",
- "@babel/plugin-transform-optional-chaining": "^7.29.7",
- "@babel/plugin-transform-parameters": "^7.29.7",
- "@babel/plugin-transform-private-methods": "^7.29.7",
- "@babel/plugin-transform-private-property-in-object": "^7.29.7",
- "@babel/plugin-transform-property-literals": "^7.29.7",
- "@babel/plugin-transform-regenerator": "^7.29.7",
- "@babel/plugin-transform-regexp-modifiers": "^7.29.7",
- "@babel/plugin-transform-reserved-words": "^7.29.7",
- "@babel/plugin-transform-shorthand-properties": "^7.29.7",
- "@babel/plugin-transform-spread": "^7.29.7",
- "@babel/plugin-transform-sticky-regex": "^7.29.7",
- "@babel/plugin-transform-template-literals": "^7.29.7",
- "@babel/plugin-transform-typeof-symbol": "^7.29.7",
- "@babel/plugin-transform-unicode-escapes": "^7.29.7",
- "@babel/plugin-transform-unicode-property-regex": "^7.29.7",
- "@babel/plugin-transform-unicode-regex": "^7.29.7",
- "@babel/plugin-transform-unicode-sets-regex": "^7.29.7",
- "@babel/preset-modules": "0.1.6-no-external-plugins",
- "babel-plugin-polyfill-corejs2": "^0.4.15",
- "babel-plugin-polyfill-corejs3": "^0.14.0",
- "babel-plugin-polyfill-regenerator": "^0.6.6",
- "core-js-compat": "^3.48.0",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/preset-modules": {
- "version": "0.1.6-no-external-plugins",
- "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
- "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/types": "^7.4.4",
- "esutils": "^2.0.2"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz",
@@ -2468,6 +1203,33 @@
}
}
},
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "11.1.6",
+ "resolved": "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz",
+ "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0||^3.0.0||^4.0.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ },
+ "tslib": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/pluginutils": {
"version": "5.4.0",
"resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
@@ -2688,6 +1450,301 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz",
+ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/type-utils": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.65.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.6.tgz",
+ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.65.0.tgz",
+ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.65.0.tgz",
+ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.65.0",
+ "@typescript-eslint/types": "^8.65.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz",
+ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz",
+ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz",
+ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.65.0.tgz",
+ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz",
+ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.65.0",
+ "@typescript-eslint/tsconfig-utils": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.8.tgz",
+ "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.65.0.tgz",
+ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.65.0",
+ "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz",
+ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.65.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@ungap/structured-clone": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
@@ -2934,48 +1991,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.17",
- "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
- "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-define-polyfill-provider": "^0.6.8",
- "semver": "^6.3.1"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.14.2",
- "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz",
- "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.8",
- "core-js-compat": "^3.48.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.6.8",
- "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz",
- "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.8"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
"node_modules/babel-preset-current-node-syntax": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
@@ -3111,6 +2126,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmmirror.com/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz",
@@ -3365,20 +2393,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/core-js-compat": {
- "version": "3.49.0",
- "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.49.0.tgz",
- "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
"node_modules/create-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmmirror.com/create-jest/-/create-jest-29.7.0.tgz",
@@ -4073,6 +3087,24 @@
"bser": "2.1.1"
}
},
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -4366,6 +3398,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/handlebars": {
+ "version": "4.7.9",
+ "resolved": "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.9.tgz",
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
@@ -5704,10 +4758,10 @@
"node": ">=8"
}
},
- "node_modules/lodash.debounce": {
- "version": "4.0.8",
- "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
"dev": true,
"license": "MIT"
},
@@ -5767,6 +4821,13 @@
"node": ">=10"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz",
@@ -5880,6 +4941,16 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@@ -5894,6 +4965,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz",
@@ -6361,64 +5439,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/regenerate": {
- "version": "1.4.2",
- "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz",
- "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regenerate-unicode-properties": {
- "version": "10.2.2",
- "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
- "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regexpu-core": {
- "version": "6.4.0",
- "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.4.0.tgz",
- "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2",
- "regenerate-unicode-properties": "^10.2.2",
- "regjsgen": "^0.8.0",
- "regjsparser": "^0.13.0",
- "unicode-match-property-ecmascript": "^2.0.0",
- "unicode-match-property-value-ecmascript": "^2.2.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regjsgen": {
- "version": "0.8.0",
- "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz",
- "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regjsparser": {
- "version": "0.13.2",
- "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.2.tgz",
- "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "jsesc": "~3.1.0"
- },
- "bin": {
- "regjsparser": "bin/parser"
- }
- },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
@@ -6980,6 +6000,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz",
@@ -7029,6 +6066,105 @@
"node": ">=12"
}
},
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.4.12",
+ "resolved": "https://registry.npmmirror.com/ts-jest/-/ts-jest-29.4.12.tgz",
+ "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.9",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.8.5",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <7"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
@@ -7079,6 +6215,20 @@
"node": ">=14.17"
}
},
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz",
@@ -7086,50 +6236,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/unicode-canonical-property-names-ecmascript": {
- "version": "2.0.1",
- "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
- "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-ecmascript": {
- "version": "2.0.0",
- "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
- "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "unicode-canonical-property-names-ecmascript": "^2.0.0",
- "unicode-property-aliases-ecmascript": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-value-ecmascript": {
- "version": "2.2.1",
- "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
- "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-property-aliases-ecmascript": {
- "version": "2.2.0",
- "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
- "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz",
@@ -7303,6 +6409,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index ce16e5d..5dbd20a 100644
--- a/package.json
+++ b/package.json
@@ -1,38 +1,38 @@
{
"name": "@metona-team/metona-toast",
- "version": "0.1.2",
- "description": "轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。",
- "main": "dist/metona-toast.js",
- "module": "src/index.js",
+ "version": "0.2.0",
+ "description": "轻量、零依赖、精致美观的Toast通知库。TypeScript源码,开箱即用。",
+ "type": "module",
+ "main": "dist/metona-toast.cjs.js",
+ "module": "dist/metona-toast.esm.js",
"unpkg": "dist/metona-toast.min.js",
"jsdelivr": "dist/metona-toast.min.js",
- "types": "types/index.d.ts",
+ "types": "dist/metona-toast.d.ts",
"exports": {
".": {
- "import": "./src/index.js",
- "require": "./dist/metona-toast.js",
- "types": "./types/index.d.ts"
+ "import": "./dist/metona-toast.esm.js",
+ "require": "./dist/metona-toast.cjs.js",
+ "types": "./dist/metona-toast.d.ts"
}
},
"files": [
"dist/",
"src/",
- "types/",
"README.md",
"LICENSE"
],
"scripts": {
- "build": "rollup -c",
+ "build": "tsc --noEmit && rollup -c",
"dev": "rollup -c -w",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
- "lint": "eslint src/",
- "lint:fix": "eslint src/ --fix",
- "format": "prettier --write src/",
+ "lint": "eslint 'src/**/*.ts'",
+ "lint:fix": "eslint 'src/**/*.ts' --fix",
+ "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'",
"typecheck": "tsc --noEmit",
- "prepublishOnly": "npm run build",
- "docs": "jsdoc src/ -d docs",
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
+ "docs": "typedoc src/ --out docs",
"example": "serve examples/"
},
"repository": {
@@ -57,14 +57,13 @@
},
"homepage": "https://git.metona.cn/MetonaTeam/MetonaToast#readme",
"devDependencies": {
- "@babel/core": "^7.22.0",
- "@babel/preset-env": "^7.22.0",
- "@babel/plugin-transform-modules-commonjs": "^7.22.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-terser": "^0.4.0",
+ "@rollup/plugin-typescript": "^11.1.6",
"@types/jest": "^29.5.0",
- "babel-jest": "^29.5.0",
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
+ "@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.40.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
@@ -73,10 +72,12 @@
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-serve": "^2.0.1",
+ "ts-jest": "^29.4.12",
+ "tslib": "^2.8.1",
"typescript": "^5.0.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16.0.0"
},
"browserslist": [
"> 1%",
diff --git a/rollup.config.js b/rollup.config.js
index 294884c..c08d5fd 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,5 +1,6 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
+import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import dts from 'rollup-plugin-dts';
import serve from 'rollup-plugin-serve';
@@ -8,128 +9,99 @@ import livereload from 'rollup-plugin-livereload';
const isDev = process.env.ROLLUP_WATCH;
const isProd = process.env.NODE_ENV === 'production';
-// 基础配置
-const baseConfig = {
- input: 'src/index.js',
- plugins: [
- resolve(),
- commonjs(),
- ],
-};
+// 基础插件
+const basePlugins = [
+ resolve(),
+ commonjs(),
+ typescript({
+ tsconfig: './tsconfig.json',
+ declaration: false,
+ sourceMap: !isProd,
+ }),
+];
// 开发服务器配置
const devPlugins = isDev ? [
serve({
open: true,
contentBase: ['site', 'dist'],
- port: 3000,
+ port: 3001,
}),
livereload({
watch: ['src', 'site'],
}),
] : [];
-// 生产环境配置
-const prodPlugins = isProd ? [
- terser({
- compress: {
- drop_console: true,
- drop_debugger: true,
- pure_funcs: ['console.log', 'console.warn'],
- },
- format: {
- comments: false,
- },
- }),
-] : [];
-
// 输出配置
export default [
- // UMD格式(浏览器)
+ // UMD(开发模式)
{
- ...baseConfig,
+ input: 'src/index.ts',
output: {
file: 'dist/metona-toast.js',
format: 'umd',
name: 'MeToast',
exports: 'named',
- sourcemap: !isProd,
- globals: {},
+ sourcemap: true,
},
- plugins: [
- ...baseConfig.plugins,
- ...devPlugins,
- ...prodPlugins,
- ],
+ plugins: [...basePlugins, ...devPlugins],
},
-
- // ESM格式(现代浏览器/打包工具)
- {
- ...baseConfig,
- output: {
- file: 'dist/metona-toast.esm.js',
- format: 'es',
- exports: 'named',
- sourcemap: !isProd,
+ // 生产构建(非 dev 模式时输出全格式)
+ ...(isDev ? [] : [
+ // ESM
+ {
+ input: 'src/index.ts',
+ output: {
+ file: 'dist/metona-toast.esm.js',
+ format: 'es',
+ exports: 'named',
+ sourcemap: true,
+ },
+ plugins: basePlugins,
},
- plugins: [
- ...baseConfig.plugins,
- ...prodPlugins,
- ],
- },
-
- // CommonJS格式(Node.js)
- {
- ...baseConfig,
- output: {
- file: 'dist/metona-toast.cjs.js',
- format: 'cjs',
- exports: 'named',
- sourcemap: !isProd,
+ // CommonJS
+ {
+ input: 'src/index.ts',
+ output: {
+ file: 'dist/metona-toast.cjs.js',
+ format: 'cjs',
+ exports: 'named',
+ sourcemap: true,
+ },
+ plugins: basePlugins,
},
- plugins: [
- ...baseConfig.plugins,
- ...prodPlugins,
- ],
- },
-
- // 压缩版本(UMD)
- {
- ...baseConfig,
- output: {
- file: 'dist/metona-toast.min.js',
- format: 'umd',
- name: 'MeToast',
- exports: 'named',
- sourcemap: false,
- globals: {},
+ // UMD 压缩版
+ {
+ input: 'src/index.ts',
+ output: {
+ file: 'dist/metona-toast.min.js',
+ format: 'umd',
+ name: 'MeToast',
+ exports: 'named',
+ sourcemap: false,
+ },
+ plugins: [
+ ...basePlugins,
+ terser({
+ compress: {
+ drop_console: true,
+ drop_debugger: true,
+ pure_funcs: ['console.log', 'console.warn'],
+ passes: 2,
+ },
+ format: { comments: false },
+ mangle: { toplevel: true },
+ }),
+ ],
},
- plugins: [
- ...baseConfig.plugins,
- terser({
- compress: {
- drop_console: true,
- drop_debugger: true,
- pure_funcs: ['console.log', 'console.warn'],
- passes: 2,
- },
- format: {
- comments: false,
- },
- mangle: {
- toplevel: true,
- },
- }),
- ],
- },
-
- // TypeScript声明文件
- {
- input: 'types/index.d.ts',
- output: {
- file: 'dist/metona-toast.d.ts',
- format: 'es',
+ // TypeScript 声明文件合并
+ {
+ input: 'src/index.ts',
+ output: {
+ file: 'dist/metona-toast.d.ts',
+ format: 'es',
+ },
+ plugins: [dts()],
},
- plugins: [dts()],
- },
+ ]),
];
diff --git a/serve.sh b/serve.sh
index f0ebe92..57b3b42 100644
--- a/serve.sh
+++ b/serve.sh
@@ -15,7 +15,7 @@ else
exit 1
fi
-PORT=${1:-3000}
+PORT=${1:-3001}
echo "✅ Python: $($PYTHON --version 2>&1)"
echo "🌐 启动服务器..."
diff --git a/site/demo.html b/site/demo.html
index ffbc685..428c9c9 100644
--- a/site/demo.html
+++ b/site/demo.html
@@ -244,7 +244,7 @@
-
🛡️ onError 错误捕获 v0.1.2
+
🛡️ onError 错误捕获 v0.2.0
@@ -282,7 +282,7 @@
diff --git a/site/docs.html b/site/docs.html
index d1bfc3d..9e320b1 100644
--- a/site/docs.html
+++ b/site/docs.html
@@ -87,7 +87,7 @@
API 文档
-MetonaToast v0.1.2 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 配置项 作为可选第二参数。
+MetonaToast v0.2.0 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 配置项 作为可选第二参数。
show(message, opts?)
diff --git a/site/index.html b/site/index.html
index 2f8bc57..8ec211b 100644
--- a/site/index.html
+++ b/site/index.html
@@ -80,7 +80,7 @@
MetonaToast
- 轻量 · 零依赖 · 精致美观的 Toast 通知库。纯原生 JavaScript,gzip 不到 10KB。
+ 轻量 · 零依赖 · 精致美观的 Toast 通知库。TypeScript 严格模式源码,gzip 不到 10KB。
📦 <10KB gzip🎨 107 图标🎭 11 动画🌍 国际化🔌 插件📘 TypeScript
@@ -126,8 +126,8 @@
⚡
-
零依赖
-
纯原生 JavaScript 实现,无需任何运行时依赖。兼容所有现代浏览器。
+
零依赖
+
TypeScript 严格模式,纯原生实现,无需任何运行时依赖。兼容所有现代浏览器。
📘
-
TypeScript
-
完整类型定义,与实现严格对齐零 phantom 方法。MeToast.success()、.confirm()、.action() 全部有类型。
+
TypeScript
+
严格模式源码,类型从源码自动生成。strictNullChecks、noImplicitAny 全开,零类型漂移。
@@ -173,8 +173,8 @@
📦
-
Node.js
-
18+
+
Node.js
+
16+
📘
@@ -247,7 +247,7 @@ orderGroup.
dismiss();
// 一键关闭
diff --git a/src/animations.js b/src/animations.js
deleted file mode 100644
index f70242d..0000000
--- a/src/animations.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * MetonaToast Animations - 动画管理 v0.1.2
- * @module animations
- * @description 动画注册与管理,移除未使用的 Web Animations API dead code
- */
-
-import { ANIMATIONS } from './constants.js';
-
-// 动画缓存
-const animationMap = new Map();
-
-/**
- * 注册默认动画
- */
-Object.entries(ANIMATIONS).forEach(([name, config]) => {
- animationMap.set(name, config);
-});
-
-/**
- * 动画工具函数
- */
-export const animationUtils = {
- /**
- * 注册自定义动画
- * @param {string} name - 动画名称
- * @param {Object} config - 动画配置 { enter, leave, duration, easing }
- */
- register(name, config) {
- animationMap.set(name, {
- name,
- enter: config.enter || {},
- leave: config.leave || {},
- duration: config.duration || 300,
- easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
- });
- },
-
- /**
- * 注销动画
- * @param {string} name - 动画名称
- */
- unregister(name) {
- animationMap.delete(name);
- },
-
- /**
- * 获取动画配置
- * @param {string} name - 动画名称
- * @returns {Object|null} 动画配置
- */
- get(name) {
- return animationMap.get(name) || ANIMATIONS[name] || null;
- },
-
- /**
- * 获取所有动画名称
- * @returns {Array} 动画名称数组
- */
- getAnimationNames() {
- return Array.from(animationMap.keys());
- },
-
- /**
- * 获取已注册动画总数
- * @returns {number} 已注册动画数量
- */
- getActiveCount() {
- return animationMap.size;
- },
-
- /**
- * 取消所有动画(兼容API,当前CSS动画由浏览器管理)
- */
- cancelAll() {
- // CSS动画由浏览器原生管理,无需手动取消
- },
-
- /**
- * 重置动画管理器
- */
- reset() {
- animationMap.clear();
- Object.entries(ANIMATIONS).forEach(([name, config]) => {
- animationMap.set(name, config);
- });
- },
-
- /**
- * 销毁动画管理器
- */
- destroy() {
- animationMap.clear();
- },
-};
-
-/**
- * 动画预设
- */
-export const animationPresets = {};
-
-/**
- * 创建自定义动画配置
- * @param {Object} config - 动画配置
- * @returns {Object} 动画配置
- */
-export const createAnimation = (config) => ({
- enter: config.enter || {},
- leave: config.leave || {},
- duration: config.duration || 300,
- easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
-});
diff --git a/src/animations.ts b/src/animations.ts
new file mode 100644
index 0000000..cb876c7
--- /dev/null
+++ b/src/animations.ts
@@ -0,0 +1,89 @@
+/**
+ * MetonaToast Animations — 动画管理
+ * @module animations
+ * @version 0.2.0
+ */
+
+import { ANIMATIONS } from './constants.js';
+import type { AnimationConfig, AnimationUtils } from './types.js';
+
+// 动画缓存
+const animationMap: Map = new Map();
+
+// 注册默认动画
+Object.entries(ANIMATIONS).forEach(([name, config]) => {
+ animationMap.set(name, {
+ name,
+ enter: config.enter,
+ leave: config.leave,
+ duration: config.duration,
+ easing: config.easing,
+ });
+});
+
+/**
+ * 动画工具函数
+ */
+export const animationUtils: AnimationUtils = {
+ register(name: string, config: Partial): void {
+ animationMap.set(name, {
+ name,
+ enter: config.enter || {},
+ leave: config.leave || {},
+ duration: config.duration || 300,
+ easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
+ });
+ },
+
+ unregister(name: string): void {
+ animationMap.delete(name);
+ },
+
+ get(name: string): AnimationConfig | null {
+ return animationMap.get(name) || null;
+ },
+
+ getAnimationNames(): string[] {
+ return Array.from(animationMap.keys());
+ },
+
+ getActiveCount(): number {
+ return animationMap.size;
+ },
+
+ cancelAll(): void {
+ // CSS动画由浏览器原生管理,无需手动取消
+ },
+
+ reset(): void {
+ animationMap.clear();
+ Object.entries(ANIMATIONS).forEach(([name, config]) => {
+ animationMap.set(name, {
+ name,
+ enter: config.enter,
+ leave: config.leave,
+ duration: config.duration,
+ easing: config.easing,
+ });
+ });
+ },
+
+ destroy(): void {
+ animationMap.clear();
+ },
+};
+
+/**
+ * 动画预设(占位,兼容旧API)
+ */
+export const animationPresets: Record = {};
+
+/**
+ * 创建自定义动画配置
+ */
+export const createAnimation = (config: Partial): AnimationConfig => ({
+ enter: config.enter || {},
+ leave: config.leave || {},
+ duration: config.duration || 300,
+ easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
+});
diff --git a/src/api.ts b/src/api.ts
new file mode 100644
index 0000000..8eb60c1
--- /dev/null
+++ b/src/api.ts
@@ -0,0 +1,617 @@
+/**
+ * MetonaToast API — meToast 核心 API 对象
+ * @module api
+ * @version 0.2.0
+ */
+
+import { Toast, _containerCache } from './toast.js';
+import { DEFAULTS } from './constants.js';
+import { escapeHTML } from './utils.js';
+import { t } from './i18n.js';
+import { applyTheme } from './themes.js';
+import { setCurrentLocale } from './i18n.js';
+import { confirmHTML, promptHTML, progressHTML, actionHTML } from './templates.js';
+import type {
+ ToastConfig, ToastOptions, ToastInstance,
+ LoadingControl, ProgressControl, CountdownControl,
+ ActionControl, QueueControl, GroupAPI,
+ ActionButton, PromiseOptions, ConfirmOptions,
+ PromptOptions, ProgressOptions, CountdownOptions,
+ QueueOptions, StackOptions, MeToast, ErrorInfo,
+} from './types.js';
+
+/**
+ * 参数标准化工具
+ */
+const normalizeArgs = (args: unknown[], defaultType = 'default'): ToastOptions => {
+ const [first, second] = args;
+
+ if (typeof first === 'string') {
+ return {
+ ...((second as Record) || {}),
+ type: (second as Record)?.type as string || defaultType,
+ message: first,
+ };
+ }
+
+ if (first && typeof first === 'object') {
+ return {
+ ...(first as Record),
+ type: (first as Record).type as string || defaultType,
+ } as ToastOptions;
+ }
+
+ return {
+ type: defaultType,
+ message: '',
+ };
+};
+
+/**
+ * meToast API 对象
+ */
+const meToast: MeToast = {
+ _toasts: new Map(),
+ _config: { ...DEFAULTS } as unknown as ToastConfig,
+ version: '0.2.0',
+
+ configure(opts: Partial): MeToast {
+ if (!opts || typeof opts !== 'object') return this;
+ Object.assign(this._config, opts);
+
+ if (opts.theme) {
+ applyTheme(opts.theme);
+ }
+
+ if (opts.locale) {
+ setCurrentLocale(opts.locale);
+ }
+
+ return this;
+ },
+
+ _emit(opts: ToastOptions): Toast {
+ const merged: ToastOptions = { ...this._config, ...opts };
+ if (merged.content && !merged.message) merged.message = merged.content;
+
+ const t = new Toast(merged);
+ t.create();
+ this._toasts.set(t.id, t);
+
+ return t;
+ },
+
+ _remove(id: string): void {
+ this._toasts.delete(id);
+ },
+
+ find(id: string): ToastInstance | undefined {
+ if (!id) return undefined;
+ return this._toasts.get(id);
+ },
+
+ show(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): ToastInstance {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'default');
+ return this._emit(normalized);
+ },
+
+ success(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): ToastInstance {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'success');
+ return this._emit(normalized);
+ },
+
+ error(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): ToastInstance {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'error');
+ return this._emit(normalized);
+ },
+
+ warning(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): ToastInstance {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'warning');
+ return this._emit(normalized);
+ },
+
+ info(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): ToastInstance {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'info');
+ return this._emit(normalized);
+ },
+
+ loading(messageOrOpts: string | ToastOptions, opts: ToastOptions = {}): LoadingControl {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'loading');
+ normalized.duration = 0;
+ normalized.closeButton = false;
+ normalized.showProgress = false;
+
+ const toast = this._emit(normalized);
+
+ return {
+ id: toast.id,
+ success: (msg?: string, o: ToastOptions = {}) => this._resolve(toast, 'success', msg || t('success'), o),
+ error: (msg?: string, o: ToastOptions = {}) => this._resolve(toast, 'error', msg || t('error'), o),
+ info: (msg?: string, o: ToastOptions = {}) => this._resolve(toast, 'info', msg || t('info'), o),
+ warning: (msg?: string, o: ToastOptions = {}) => this._resolve(toast, 'warning', msg || t('warning'), o),
+ update: (p: Partial) => { toast.update(p); return this; },
+ dismiss: () => toast.close(),
+ };
+ },
+
+ promise(promise: Promise, opts: PromiseOptions = {}): Promise {
+ if (!promise || typeof (promise as unknown as { then?: unknown }).then !== 'function') {
+ console.error('MeToast.promise: first argument must be a Promise');
+ return Promise.reject(new Error('Invalid promise'));
+ }
+
+ const loadingMsg = opts.loading || t('loading');
+ const successMsg = opts.success || t('success');
+ const errorMsg = opts.error || t('error');
+
+ const ctrl = this.loading(loadingMsg);
+
+ return Promise.resolve(promise)
+ .then((data: T) => {
+ ctrl.success(successMsg);
+ return data;
+ })
+ .catch((err: unknown) => {
+ ctrl.error(errorMsg);
+ throw err;
+ });
+ },
+
+ _resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null {
+ const id = loadingToast.id;
+ const old = this._toasts.get(id);
+ if (!old) return null;
+
+ const position = old.config.position;
+ old.close();
+
+ return this._emit({ ...opts, type, message, position } as ToastOptions);
+ },
+
+ confirm(message: string, opts: ConfirmOptions = {}): Promise {
+ if (typeof message !== 'string') {
+ console.error('MeToast.confirm: message must be a string');
+ return Promise.resolve(false);
+ }
+
+ return new Promise((resolve) => {
+ let resolved = false;
+ const safeResolve = (val: boolean) => {
+ if (!resolved) { resolved = true; clearTimeout(safetyTimeout); resolve(val); }
+ };
+ const safetyTimeout = setTimeout(() => safeResolve(false), 10000);
+
+ const toast = this._emit({
+ ...opts,
+ type: opts.type || 'warning',
+ message,
+ duration: 0,
+ closeButton: false,
+ closeOnClick: false,
+ draggable: false,
+ html: confirmHTML(opts),
+ });
+
+ setTimeout(() => {
+ const confirmBtn = toast.el?.querySelector('.met-confirm-btn') as HTMLElement | null;
+ const cancelBtn = toast.el?.querySelector('.met-cancel-btn') as HTMLElement | null;
+
+ if (confirmBtn) {
+ confirmBtn.addEventListener('click', () => {
+ toast.close();
+ safeResolve(true);
+ });
+ }
+
+ if (cancelBtn) {
+ cancelBtn.addEventListener('click', () => {
+ toast.close();
+ safeResolve(false);
+ });
+ }
+ }, 0);
+ });
+ },
+
+ prompt(message: string, opts: PromptOptions = {}): Promise {
+ if (typeof message !== 'string') {
+ console.error('MeToast.prompt: message must be a string');
+ return Promise.resolve(null);
+ }
+
+ return new Promise((resolve) => {
+ let resolved = false;
+ const safeResolve = (val: string | null) => {
+ if (!resolved) { resolved = true; clearTimeout(safetyTimeout); resolve(val); }
+ };
+ const safetyTimeout = setTimeout(() => safeResolve(null), 10000);
+
+ const toast = this._emit({
+ ...opts,
+ type: opts.type || 'info',
+ message,
+ duration: 0,
+ closeButton: false,
+ closeOnClick: false,
+ draggable: false,
+ html: promptHTML(opts),
+ });
+
+ setTimeout(() => {
+ const input = toast.el?.querySelector('.met-input') as HTMLInputElement | null;
+ const submitBtn = toast.el?.querySelector('.met-submit-btn') as HTMLElement | null;
+ const cancelBtn = toast.el?.querySelector('.met-cancel-btn') as HTMLElement | null;
+
+ if (input) {
+ input.focus();
+ input.addEventListener('keydown', (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ toast.close();
+ safeResolve(input.value);
+ }
+ });
+ }
+
+ if (submitBtn) {
+ submitBtn.addEventListener('click', () => {
+ toast.close();
+ safeResolve(input?.value || null);
+ });
+ }
+
+ if (cancelBtn) {
+ cancelBtn.addEventListener('click', () => {
+ toast.close();
+ safeResolve(null);
+ });
+ }
+ }, 0);
+ });
+ },
+
+ progress(messageOrOpts: string | ToastOptions, opts: ProgressOptions = {}): ProgressControl {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'info');
+ normalized.duration = 0;
+ normalized.closeButton = false;
+ normalized.showProgress = false;
+
+ const toast = this._emit({
+ ...normalized,
+ html: progressHTML(opts),
+ });
+
+ return {
+ id: toast.id,
+
+ setProgress(percent: number) {
+ const fill = toast.el?.querySelector('.met-progress-fill') as HTMLElement | null;
+ const text = toast.el?.querySelector('.met-progress-text') as HTMLElement | null;
+
+ if (fill) {
+ fill.style.width = `${Math.min(100, Math.max(0, percent))}%`;
+ }
+ if (text) {
+ text.textContent = `${Math.round(percent)}%`;
+ }
+ },
+
+ complete(message?: string) {
+ this.setProgress(100);
+ setTimeout(() => {
+ toast.update({
+ type: 'success',
+ message: message || t('success'),
+ html: '',
+ });
+ setTimeout(() => toast.close(), 1000);
+ }, 300);
+ },
+
+ error(message?: string) {
+ toast.update({
+ type: 'error',
+ message: message || t('error'),
+ html: '',
+ });
+ setTimeout(() => toast.close(), 2000);
+ },
+
+ dismiss() {
+ toast.close();
+ },
+ };
+ },
+
+ countdown(message: string, seconds = 10, opts: CountdownOptions = {}): CountdownControl {
+ if (typeof message !== 'string') {
+ console.error('MeToast.countdown: message must be a string');
+ return { id: '', cancel: () => {}, pause: () => {}, resume: () => {} };
+ }
+
+ let remaining = Math.max(1, parseInt(String(seconds)) || 10);
+ let timer: ReturnType | null = null;
+
+ const toast = this._emit({
+ ...opts,
+ type: opts.type || 'warning',
+ message: message.replace(/\{seconds\}/g, String(remaining)),
+ duration: 0,
+ closeButton: true,
+ showProgress: false,
+ });
+
+ const tick = (): void => {
+ remaining--;
+
+ if (remaining <= 0) {
+ if (timer !== null) clearInterval(timer);
+ toast.close();
+
+ if (typeof opts.onComplete === 'function') {
+ try { opts.onComplete(); } catch (e) { console.error('countdown onComplete error:', e); }
+ }
+ return;
+ }
+
+ toast.update({
+ message: message.replace(/\{seconds\}/g, String(remaining)),
+ });
+ };
+
+ timer = setInterval(tick, 1000);
+
+ return {
+ id: toast.id,
+ cancel() { if (timer !== null) clearInterval(timer); toast.close(); },
+ pause() { if (timer !== null) clearInterval(timer); },
+ resume() { if (timer !== null) clearInterval(timer); timer = setInterval(tick, 1000); },
+ };
+ },
+
+ action(messageOrOpts: string | ToastOptions, actions: ActionButton[] = [], opts: ToastOptions = {}): ActionControl {
+ const normalized = normalizeArgs([messageOrOpts, opts], 'info');
+ normalized.duration = opts.duration ?? 0;
+ normalized.closeButton = opts.closeButton ?? true;
+
+ const toast = this._emit({
+ ...normalized,
+ html: (normalized.html || '') + actionHTML(actions),
+ });
+
+ if (Array.isArray(actions)) {
+ setTimeout(() => {
+ actions.forEach((a, i) => {
+ const btn = toast.el?.querySelector(`.met-action-btn-${i}`) as HTMLElement | null;
+ if (btn && typeof a.onClick === 'function') {
+ btn.addEventListener('click', () => {
+ try { a.onClick(toast); } catch (e) { console.error('Action onClick error:', e); }
+ if (a.close !== false) toast.close();
+ });
+ }
+ });
+ }, 0);
+ }
+
+ return { id: toast.id, toast, dismiss: () => toast.close() };
+ },
+
+ queue(messages: Array, opts: QueueOptions = {}): QueueControl {
+ if (!Array.isArray(messages)) {
+ console.error('MeToast.queue: messages must be an array');
+ const p = Promise.resolve();
+ return { then: (fn) => p.then(fn), catch: (rj) => p.catch(rj), cancel: () => {} };
+ }
+
+ let cancelled = false;
+ const promise = new Promise((resolve) => {
+ let index = 0;
+ const delay = opts.delay || 1000;
+ const userOnClose = opts.onClose;
+
+ const showNext = (): void => {
+ if (cancelled || index >= messages.length) {
+ resolve();
+ return;
+ }
+
+ const message = messages[index];
+ index++;
+
+ const msg = typeof message === 'string' ? message : ((message as ToastOptions)?.message || '');
+ const msgObj: ToastOptions = (typeof message === 'object' && message !== null) ? message as ToastOptions : {};
+ const msgOnClose = msgObj.onClose;
+
+ this._emit({
+ ...opts,
+ ...msgObj,
+ message: msg,
+ duration: msgObj.duration || opts.duration || 3000,
+ onClose: (toast: ToastInstance) => {
+ if (typeof msgOnClose === 'function') {
+ try { msgOnClose(toast); } catch (_e) { /* noop */ }
+ }
+ if (typeof userOnClose === 'function') {
+ try { userOnClose(toast); } catch (_e) { /* noop */ }
+ }
+ setTimeout(showNext, delay);
+ },
+ } as ToastOptions);
+ };
+
+ showNext();
+ });
+
+ return {
+ then: (fn, rj) => promise.then(fn, rj),
+ catch: (rj) => promise.catch(rj),
+ cancel: () => { cancelled = true; },
+ };
+ },
+
+ stack(messages: Array, opts: StackOptions = {}): void {
+ if (!Array.isArray(messages)) {
+ console.error('MeToast.stack: messages must be an array');
+ return;
+ }
+
+ messages.forEach((message, index) => {
+ setTimeout(() => {
+ const msg = typeof message === 'string' ? message : ((message as ToastOptions)?.message || '');
+
+ this._emit({
+ ...opts,
+ ...((typeof message === 'object' && message !== null) ? message as ToastOptions : {}),
+ message: msg,
+ } as ToastOptions);
+ }, index * (opts.stagger || 100));
+ });
+ },
+
+ dismiss(id?: string): void {
+ if (id) {
+ const t = this._toasts.get(id);
+ if (t) t.close();
+ return;
+ }
+ this._toasts.forEach(t => t.close());
+ },
+
+ clear(position?: string): void {
+ this._toasts.forEach(t => {
+ if (!position || t.config.position === position) t.close();
+ });
+ },
+
+ destroy(): void {
+ this.dismiss();
+
+ if (typeof document !== 'undefined') {
+ const containers = document.querySelectorAll('.met-container');
+ containers.forEach(c => { if (c.parentNode) c.parentNode.removeChild(c); });
+
+ const style = document.getElementById('metona-toast-styles');
+ if (style && style.parentNode) style.parentNode.removeChild(style);
+ }
+
+ this._toasts.clear();
+ _containerCache.clear();
+ },
+
+ getAll(): Map {
+ return new Map(this._toasts);
+ },
+
+ count(): number {
+ return this._toasts.size;
+ },
+
+ group(name: string): GroupAPI {
+ const self = this;
+ const methods = ['show', 'success', 'error', 'warning', 'info', 'loading', 'action'];
+ const g: GroupAPI = { _group: name } as unknown as GroupAPI;
+ methods.forEach(m => {
+ (g as unknown as Record unknown>)[m] = (...args: unknown[]) => {
+ const lastArg = args[args.length - 1];
+ const isObj = typeof lastArg === 'object' && lastArg !== null && !Array.isArray(lastArg);
+ const selfMethods = self as unknown as Record unknown>;
+ if (isObj && args.length === 1) {
+ return selfMethods[m]({ ...(lastArg as Record), group: name });
+ }
+ if (isObj) {
+ args.pop();
+ return selfMethods[m](...args, { ...(lastArg as Record), group: name });
+ }
+ return selfMethods[m](...args, { group: name });
+ };
+ });
+ g.dismiss = () => self.dismissGroup(name);
+ g.count = () => self._groupCount(name);
+ return g;
+ },
+
+ dismissGroup(name: string): void {
+ this._toasts.forEach(t => { if (t.group === name) t.close(); });
+ },
+
+ _groupCount(name: string): number {
+ let c = 0;
+ this._toasts.forEach(t => { if (t.group === name) c++; });
+ return c;
+ },
+
+ getToasts(): ToastInstance[] {
+ return Array.from(this._toasts.values());
+ },
+
+ hasToasts(): boolean {
+ return this._toasts.size > 0;
+ },
+
+ getToast(id: string): ToastInstance | null {
+ if (!id) return null;
+ return this._toasts.get(id) || null;
+ },
+
+ closeAll(): void {
+ this._toasts.forEach(t => t.close());
+ },
+
+ clearAll(): void {
+ this._toasts.forEach(t => t.close());
+ },
+
+ pauseAll(): void {
+ this._toasts.forEach(t => t._pause());
+ },
+
+ resumeAll(): void {
+ this._toasts.forEach(t => t._resume());
+ },
+
+ updateAll(partial: Partial): void {
+ if (partial && typeof partial === 'object') {
+ this._toasts.forEach(t => t.update(partial));
+ }
+ },
+
+ findToasts(predicate: (toast: ToastInstance) => boolean): ToastInstance[] {
+ if (typeof predicate !== 'function') return [];
+ return Array.from(this._toasts.values()).filter(predicate);
+ },
+
+ findByType(type: string): ToastInstance[] {
+ return this.findToasts(t => t.type === type);
+ },
+
+ findByPosition(position: string): ToastInstance[] {
+ return this.findToasts(t => t.config.position === position);
+ },
+
+ // ====== 以下由 index.ts 增强 ======
+
+ init(_options?: Record): MeToast { return this; },
+ getStatus() { return { version: '0.2.0', toasts: 0, theme: '', locale: '', plugins: [], animations: 0 }; },
+ getConfig(): ToastConfig { return { ...this._config }; },
+ updateConfig(_config: Partial): MeToast { return this; },
+ resetConfig(): MeToast { return this; },
+ use(_plugin: string | Record, _options?: Record): MeToast { return this; },
+
+ animations: null as unknown as MeToast['animations'],
+ themes: null as unknown as MeToast['themes'],
+ i18n: null as unknown as MeToast['i18n'],
+ plugins: null as unknown as MeToast['plugins'],
+ presetPlugins: {} as MeToast['presetPlugins'],
+};
+
+// 连接 Toast 静态回调到 meToast 实例
+Toast._onError = (info: ErrorInfo) => {
+ const onError = meToast._config.onError;
+ if (typeof onError === 'function') {
+ try { onError(info); } catch (_e) { /* noop */ }
+ }
+};
+Toast._removeToast = (id: string) => {
+ meToast._toasts.delete(id);
+};
+
+export { meToast as default, meToast };
diff --git a/src/constants.js b/src/constants.ts
similarity index 82%
rename from src/constants.js
rename to src/constants.ts
index 57bf979..25fc3b9 100644
--- a/src/constants.js
+++ b/src/constants.ts
@@ -1,72 +1,51 @@
/**
- * MetonaToast Constants - 常量定义
+ * MetonaToast Constants — 常量定义
* @module constants
- * @version 0.1.2
- * @description 默认配置、颜色、动画、主题等常量
+ * @version 0.2.0
*/
+import { ICONS } from './icons.js';
+import { LOCALES } from './locales.js';
+
+export { ICONS, LOCALES };
+
/**
* 默认配置
*/
export const DEFAULTS = Object.freeze({
- // 基础配置
position: 'top-right',
duration: 4000,
max: 6,
gap: 12,
offset: 24,
-
- // 交互配置
pauseOnHover: true,
closeOnClick: true,
draggable: true,
-
- // 显示配置
showProgress: true,
- progressDirection: 'horizontal',
+ progressDirection: 'horizontal' as const,
icon: true,
closeButton: true,
-
- // 主题配置
theme: 'auto',
animation: 'slide',
-
- // 布局配置
zIndex: 9999,
width: 360,
-
- // 自定义配置
className: '',
- style: {},
-
- // 回调函数
- onShow: null,
- onClose: null,
- onClick: null,
- onUpdate: null,
-
- // 高级
+ style: {} as Record,
+ onShow: null as ((...args: unknown[]) => void) | null,
+ onClose: null as ((...args: unknown[]) => void) | null,
+ onClick: null as ((...args: unknown[]) => void) | null,
+ onUpdate: null as ((...args: unknown[]) => void) | null,
resetTimerOnUpdate: false,
notifyWhenHidden: false,
-
- // 错误处理
- onError: null,
-
- // 国际化
+ onError: null as ((...args: unknown[]) => void) | null,
locale: 'zh-CN',
-
- // 插件
- plugins: [],
+ plugins: [] as Array>,
});
-// Icons — 从 icons.js 导入并重新导出(保持向后兼容)
-import { ICONS } from './icons.js';
-export { ICONS };
-
/**
* 类型颜色配置
*/
-export const TYPE_COLORS = {
+export const TYPE_COLORS: Record = {
success: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
error: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
warning: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
@@ -179,70 +158,75 @@ export const TYPE_COLORS = {
/**
* 动画配置
*/
-export const ANIMATIONS = {
+export const ANIMATIONS: Record;
+ leave: Record;
+ duration: number;
+ easing: string;
+}> = {
slide: {
- enter: { transform: 'translateX(80px)', opacity: 0 },
- leave: { transform: 'translateX(120%)', opacity: 0 },
+ enter: { transform: 'translateX(80px)', opacity: '0' },
+ leave: { transform: 'translateX(120%)', opacity: '0' },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
fade: {
- enter: { opacity: 0, filter: 'blur(3px)' },
- leave: { opacity: 0, filter: 'blur(3px)' },
+ enter: { opacity: '0', filter: 'blur(3px)' },
+ leave: { opacity: '0', filter: 'blur(3px)' },
duration: 500,
easing: 'ease',
},
scale: {
- enter: { transform: 'scale(0.55)', opacity: 0 },
- leave: { transform: 'scale(0.55)', opacity: 0 },
+ enter: { transform: 'scale(0.55)', opacity: '0' },
+ leave: { transform: 'scale(0.55)', opacity: '0' },
duration: 450,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
bounce: {
- enter: { transform: 'translateY(-80px)', opacity: 0 },
- leave: { transform: 'translateY(20px)', opacity: 0 },
+ enter: { transform: 'translateY(-80px)', opacity: '0' },
+ leave: { transform: 'translateY(20px)', opacity: '0' },
duration: 650,
easing: 'ease',
},
flip: {
- enter: { transform: 'perspective(500px) rotateX(-90deg)', opacity: 0 },
- leave: { transform: 'perspective(500px) rotateX(90deg)', opacity: 0 },
+ enter: { transform: 'perspective(500px) rotateX(-90deg)', opacity: '0' },
+ leave: { transform: 'perspective(500px) rotateX(90deg)', opacity: '0' },
duration: 500,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
rotate: {
- enter: { transform: 'rotate(-25deg) scale(0.6)', opacity: 0 },
- leave: { transform: 'rotate(25deg) scale(0.6)', opacity: 0 },
+ enter: { transform: 'rotate(-25deg) scale(0.6)', opacity: '0' },
+ leave: { transform: 'rotate(25deg) scale(0.6)', opacity: '0' },
duration: 500,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
zoom: {
- enter: { transform: 'scale(0.1)', opacity: 0 },
- leave: { transform: 'scale(0.1)', opacity: 0 },
+ enter: { transform: 'scale(0.1)', opacity: '0' },
+ leave: { transform: 'scale(0.1)', opacity: '0' },
duration: 500,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
slideUp: {
- enter: { transform: 'translateY(60px)', opacity: 0 },
- leave: { transform: 'translateY(-120%)', opacity: 0 },
+ enter: { transform: 'translateY(60px)', opacity: '0' },
+ leave: { transform: 'translateY(-120%)', opacity: '0' },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideDown: {
- enter: { transform: 'translateY(-60px)', opacity: 0 },
- leave: { transform: 'translateY(120%)', opacity: 0 },
+ enter: { transform: 'translateY(-60px)', opacity: '0' },
+ leave: { transform: 'translateY(120%)', opacity: '0' },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideLeft: {
- enter: { transform: 'translateX(-90px)', opacity: 0 },
- leave: { transform: 'translateX(120%)', opacity: 0 },
+ enter: { transform: 'translateX(-90px)', opacity: '0' },
+ leave: { transform: 'translateX(120%)', opacity: '0' },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideRight: {
- enter: { transform: 'translateX(90px)', opacity: 0 },
- leave: { transform: 'translateX(-120%)', opacity: 0 },
+ enter: { transform: 'translateX(90px)', opacity: '0' },
+ leave: { transform: 'translateX(-120%)', opacity: '0' },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
@@ -251,7 +235,15 @@ export const ANIMATIONS = {
/**
* 主题配置
*/
-export const THEMES = {
+export const THEMES: Record = {
light: {
bg: 'rgba(255, 255, 255, 0.96)',
text: '#1f2937',
@@ -291,7 +283,7 @@ export const THEMES = {
};
/**
- * 位置配置
+ * 位置列表
*/
export const POSITIONS = [
'top-left',
@@ -300,10 +292,10 @@ export const POSITIONS = [
'bottom-left',
'bottom-center',
'bottom-right',
-];
+] as const;
/**
- * 类型配置
+ * 类型列表
*/
export const TYPES = [
'default', 'success', 'error', 'warning', 'info', 'loading',
@@ -322,23 +314,19 @@ export const TYPES = [
'checkCircle', 'xCircle', 'alertCircle', 'infoCircle', 'helpCircle',
'alertTriangle', 'checkSquare', 'square', 'circle', 'triangle',
'hexagon', 'octagon', 'pentagon', 'diamond',
-];
-
-// Locales — 从 locales.js 导入并重新导出(保持向后兼容)
-import { LOCALES } from './locales.js';
-export { LOCALES };
+] as const;
/**
* 进度条方向
*/
-export const PROGRESS_DIRECTIONS = ['horizontal', 'vertical'];
+export const PROGRESS_DIRECTIONS = ['horizontal', 'vertical'] as const;
/**
* 动画类型
*/
-export const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
+export const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'] as const;
/**
* 主题类型
*/
-export const THEME_TYPES = ['light', 'dark', 'auto'];
+export const THEME_TYPES = ['light', 'dark', 'auto'] as const;
diff --git a/src/core.js b/src/core.js
deleted file mode 100644
index 6a645cd..0000000
--- a/src/core.js
+++ /dev/null
@@ -1,1243 +0,0 @@
-/**
- * MetonaToast Core - 核心Toast逻辑
- * @module core
- * @version 0.1.2
- * @description 重构后的核心模块,包含Toast类的优化实现
- */
-
-import { generateId, escapeHTML } from './utils.js';
-import { DEFAULTS, ICONS, TYPE_COLORS, THEMES } from './constants.js';
-import { injectStyles } from './styles.js';
-import { getTheme, applyTheme } from './themes.js';
-import { t as i18nT, setCurrentLocale, getLocaleDirection, getCurrentLocale } from './i18n.js';
-
-// 模块级共享容器缓存,所有 Toast 实例共用
-// 使用 Map 而非 WeakMap:document.body 在页面存活期间永不被 GC,
-// 容器需要显式通过 destroy() 清理,WeakMap 在此场景无实际收益。
-const _containerCache = new Map();
-
-/**
- * 参数标准化工具
- * 支持多种调用方式:
- * - success('message')
- * - success({ message: 'text' })
- * - success({ title: 'title', message: 'text' })
- * - success('message', { duration: 5000 })
- */
-const normalizeArgs = (args, defaultType = 'default') => {
- const [first, second] = args;
-
- // 情况1: success('message')
- if (typeof first === 'string') {
- return {
- ...second,
- type: second?.type || defaultType,
- message: first,
- };
- }
-
- // 情况2: success({ message: 'text' }) 或 success({ title: 't', message: 'm' })
- if (first && typeof first === 'object') {
- return {
- ...first,
- type: first.type || defaultType,
- };
- }
-
- // 情况3: success() 无参数
- return {
- type: defaultType,
- message: '',
- };
-};
-
-/**
- * Toast类 - 核心通知组件
- */
-class Toast {
- // 静态钩子系统
- static _hooks = new Map();
- static on(name, fn) {
- if (!this._hooks.has(name)) this._hooks.set(name, []);
- this._hooks.get(name).push(fn);
- return () => this.off(name, fn);
- }
- static off(name, fn) {
- const list = this._hooks.get(name);
- if (list) this._hooks.set(name, list.filter(f => f !== fn));
- }
- static trigger(name, toast) {
- const list = this._hooks.get(name);
- if (list) list.forEach(fn => {
- try { fn(toast); }
- catch (e) {
- console.error('Hook error:', name, e);
- if (meToast._config.onError) {
- try { meToast._config.onError({ hook: name, error: e, toast }); } catch (_) {}
- }
- }
- });
- }
-
- constructor(opts) {
- this.id = opts.id || generateId();
- this.type = opts.type || 'default';
- this.title = opts.title || '';
- this.message = opts.message ?? opts.content ?? '';
- this.html = opts.html || '';
- this.iconHTML = opts.iconHTML || '';
- this.config = { ...DEFAULTS, ...opts };
- // 防止嵌套对象被多个 toast 实例共享引用
- if (opts.style && typeof opts.style === 'object') {
- this.config.style = { ...opts.style };
- }
- this.el = null;
- this.barEl = null;
- this.rafId = null;
- this.remaining = this.config.duration;
- this.startedAt = 0;
- this.paused = false;
- this.closing = false;
- this._cleanups = [];
- this.group = opts.group || null;
- }
-
- _palette() {
- const theme = getTheme(this.config.theme);
- const c = TYPE_COLORS[this.type] || TYPE_COLORS.default;
- const tc = THEMES[theme] || THEMES.light;
- const t = { bg: tc.bg, border: tc.border, shadow: tc.shadow };
- return { theme, c, t };
- }
-
- create() {
- if (typeof window === 'undefined' || typeof document === 'undefined') return this;
-
- Toast.trigger('beforeShow', this);
- injectStyles();
-
- const container = this._getContainer();
- const { theme, c, t } = this._palette();
-
- this._limitToasts(container);
-
- const el = document.createElement('div');
- el.className = this._buildClassName(theme);
- el.setAttribute('role', (this.type === 'error' || this.type === 'warning') ? 'alert' : 'status');
- el.setAttribute('aria-live', this.type === 'error' ? 'assertive' : 'polite');
- el.dataset.id = this.id;
-
- this._applyStyles(el, theme, t);
- this._buildContent(el, c);
-
- this.el = el;
- // Chrome bug: column-reverse + appendChild 会导致重叠。改用 column + insertBefore
- const pos = this.config.position || 'top-right';
- if (pos.startsWith('top')) {
- container.insertBefore(el, container.firstChild);
- } else {
- container.appendChild(el);
- }
-
- this._bindEvents(el);
- this._startTimer();
-
- // 进入动画
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- el.classList.add('met-show');
- });
- });
-
- if (typeof this.config.onShow === 'function') {
- try {
- this.config.onShow(this);
- } catch (e) {
- console.error('onShow callback error:', e);
- }
- }
-
- Toast.trigger('afterShow', this);
-
- return this;
- }
-
- _getContainer() {
- const position = this.config.position || 'top-right';
- const zIndex = this.config.zIndex || 9999;
-
- if (_containerCache.has(document.body)) {
- const containers = _containerCache.get(document.body);
- if (containers.has(position)) {
- return containers.get(position);
- }
- } else {
- _containerCache.set(document.body, new Map());
- }
-
- const el = document.createElement('div');
- el.className = `met-container ${position}`;
- el.setAttribute('aria-label', 'Notifications');
- el.setAttribute('role', 'region');
-
- // cssText 完全接管布局,不依赖 CSS class
- const posStyles = {
- 'top-left': 'top:0;left:0;align-items:flex-start',
- 'top-center': 'top:0;left:0;right:0;align-items:center',
- 'top-right': 'top:0;right:0;align-items:flex-end',
- 'bottom-left': 'bottom:0;left:0;align-items:flex-start',
- 'bottom-center':'bottom:0;left:0;right:0;align-items:center',
- 'bottom-right': 'bottom:0;right:0;align-items:flex-end',
- };
- el.style.cssText = `
- display:flex;
- flex-direction:column;
- gap:${this.config.gap || 12}px;
- box-sizing:border-box;
- padding:${this.config.offset || 24}px;
- max-width:100vw;
- position:fixed;
- z-index:${zIndex};
- pointer-events:none;
- ${posStyles[position] || 'top:0;right:0;align-items:flex-end'}
- `;
-
- document.body.appendChild(el);
- _containerCache.get(document.body).set(position, el);
-
- return el;
- }
-
- _limitToasts(container) {
- const max = this.config.max || 6;
- const list = Array.from(container.querySelectorAll('.met-toast'));
- if (list.length >= max) {
- const first = list[0];
- const id = first && first.dataset.id;
- if (id) {
- const old = meToast._toasts.get(id);
- if (old) old.close(true);
- }
- }
- }
-
- _buildClassName(theme) {
- // CSS 支持的动画类型,未列出的 fallback 到 slide
- const CSS_ANIMS = ['slide','fade','scale','bounce','flip','rotate','zoom',
- 'slideUp','slideDown','slideLeft','slideRight'];
- const anim = CSS_ANIMS.includes(this.config.animation) ? this.config.animation : 'slide';
- return [
- 'met-toast',
- `met-${this.type}`,
- `met-theme-${theme}`,
- `met-anim-${anim}`,
- (this.config.closeOnClick || this.config.draggable) ? 'met-clickable' : '',
- this.config.className || '',
- ].filter(Boolean).join(' ');
- }
-
- _applyStyles(el, theme, t) {
- const widthValue = typeof this.config.width === 'number'
- ? `${this.config.width}px`
- : (typeof this.config.width === 'string' ? this.config.width : '360px');
-
- // 只强制核心布局属性,动画/滤镜/颜色由 CSS class 控制
- const set = (p, v) => el.style.setProperty(p, v, 'important');
- set('position', 'relative');
- set('flex-shrink', '0');
- set('min-width', '240px');
- set('max-width', 'calc(100vw - 48px)');
- set('width', widthValue);
- // 用户自定义样式最后应用
- if (this.config.style && Object.keys(this.config.style).length > 0) {
- Object.entries(this.config.style).forEach(([k, v]) => { el.style[k] = v; });
- }
- }
-
- _buildContent(el, c) {
- // 自定义渲染函数 — 完全接管 DOM 构建
- if (typeof this.config.render === 'function') {
- el.innerHTML = this.config.render(this);
- this.barEl = el.querySelector('.met-bar, .met-bar-v');
- return;
- }
-
- const showIcon = this.config.icon !== false && (this.iconHTML || ICONS[this.type]);
- const showClose = this.config.closeButton !== false;
- const showProgress = this.config.showProgress !== false && this.config.duration > 0;
- const showSide = (!showIcon && this.type !== 'default');
-
- const safeTitle = this.title ? `${escapeHTML(this.title)}
` : '';
- const safeMessage = this.html
- ? `${this.html}
`
- : (this.message ? `${escapeHTML(this.message)}
` : '');
-
- const iconHTML = showIcon
- ? `${this.iconHTML || ICONS[this.type]}
` : '';
- const closeHTML = showClose
- ? ``
- : '';
- const progressHTML = showProgress
- ? (this.config.progressDirection === 'vertical'
- ? ``
- : ``)
- : '';
- const sideHTML = showSide
- ? `` : '';
-
- el.innerHTML = `
- ${iconHTML}
- ${sideHTML}
-
- ${safeTitle}
- ${safeMessage}
-
- ${closeHTML}
- ${progressHTML}
- `;
-
- this.barEl = el.querySelector('.met-bar, .met-bar-v');
- }
-
- _bindEvents(el) {
- const eventHandler = (e) => {
- const target = e.target;
-
- if (target.closest('.met-close')) {
- e.stopPropagation();
- this.close();
- return;
- }
-
- if (this.config.closeOnClick && !target.closest('.met-close')) {
- if (typeof this.config.onClick === 'function') {
- try {
- this.config.onClick(this);
- } catch (err) {
- console.error('onClick callback error:', err);
- }
- }
- this.close();
- return;
- }
- };
-
- el.addEventListener('click', eventHandler);
- this._cleanups.push(() => el.removeEventListener('click', eventHandler));
-
- if (this.config.pauseOnHover && this.config.duration > 0) {
- const mouseEnter = () => this._pause();
- const mouseLeave = () => this._resume();
-
- el.addEventListener('mouseenter', mouseEnter);
- el.addEventListener('mouseleave', mouseLeave);
-
- this._cleanups.push(() => {
- el.removeEventListener('mouseenter', mouseEnter);
- el.removeEventListener('mouseleave', mouseLeave);
- });
- }
-
- if (this.config.draggable) {
- this._bindDrag(el);
- }
- }
-
- _bindDrag(el) {
- let sx = 0, sy = 0, dx = 0, dy = 0, dragging = false;
-
- const down = (e) => {
- if (e.target.closest('.met-close')) return;
- dragging = true;
- sx = e.clientX;
- sy = e.clientY;
- el.setPointerCapture(e.pointerId);
- el.style.transition = 'none';
- this._pause();
- };
-
- const move = (e) => {
- if (!dragging) return;
- dx = e.clientX - sx;
- dy = e.clientY - sy;
- el.style.transform = `translate(${dx}px, ${dy}px) rotate(${dx * 0.1}deg)`;
- el.style.opacity = String(Math.max(0, 1 - Math.abs(dx) / 200));
- };
-
- const up = (e) => {
- if (!dragging) return;
- dragging = false;
- el.releasePointerCapture(e.pointerId);
- el.style.transition = '';
-
- if (Math.abs(dx) > 120) {
- el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
- el.style.opacity = '0';
- setTimeout(() => this.close(true), 250);
- } else {
- el.style.transform = '';
- el.style.opacity = '';
- this._resume();
- }
- dx = dy = 0;
- };
-
- el.addEventListener('pointerdown', down);
- el.addEventListener('pointermove', move);
- el.addEventListener('pointerup', up);
- el.addEventListener('pointercancel', up);
-
- this._cleanups.push(() => {
- el.removeEventListener('pointerdown', down);
- el.removeEventListener('pointermove', move);
- el.removeEventListener('pointerup', up);
- el.removeEventListener('pointercancel', up);
- });
- }
-
- _startTimer(resuming = false) {
- if (this.config.duration <= 0) return;
-
- // 仅在首次启动时重置时间基准;resume 场景由 _resume() 预先调整 startedAt
- if (!resuming) {
- this.startedAt = Date.now();
- this.remaining = this.config.duration;
- }
-
- const tick = () => {
- if (this.paused || this.closing) return;
-
- try {
- const elapsed = Date.now() - this.startedAt;
- this.remaining = Math.max(0, this.config.duration - elapsed);
-
- if (this.barEl) {
- const ratio = this.remaining / this.config.duration;
- const t = this.config.progressDirection === 'vertical'
- ? `scaleY(${ratio})` : `scaleX(${ratio})`;
- this.barEl.style.transform = t;
- }
-
- if (this.remaining <= 0) {
- this.close();
- return;
- }
- } catch (e) {
- console.error('Timer tick error:', e);
- if (meToast._config.onError) {
- try { meToast._config.onError({ source: 'timer', error: e, toast: this }); } catch (_) {}
- }
- }
-
- this.rafId = requestAnimationFrame(tick);
- };
-
- this.rafId = requestAnimationFrame(tick);
- }
-
- _pause() {
- if (this.paused || this.config.duration <= 0) return;
- this.paused = true;
- cancelAnimationFrame(this.rafId);
- this.remaining = Math.max(0, this.config.duration - (Date.now() - this.startedAt));
- }
-
- _resume() {
- if (!this.paused) return;
- this.paused = false;
- // 校准时间基准,补偿暂停期间经过的时间
- this.startedAt = Date.now() - (this.config.duration - this.remaining);
- this._startTimer(true);
- }
-
- update(partial) {
- Toast.trigger('beforeUpdate', this);
- const typeChanged = partial.type && partial.type !== this.type;
- if (partial.type) this.type = partial.type;
- if (partial.title !== undefined) this.title = partial.title;
- if (partial.message !== undefined) this.message = partial.message;
- if (partial.html !== undefined) this.html = partial.html;
-
- if (!this.el) { Toast.trigger('afterUpdate', this); return this; }
-
- // 类型变更时同步更新 DOM 类名、边框颜色和进度条颜色
- if (typeChanged) {
- const typeClasses = ['met-success', 'met-error', 'met-warning', 'met-info', 'met-loading', 'met-default'];
- typeClasses.forEach(c => this.el.classList.remove(c));
- this.el.classList.add(`met-${this.type}`);
- if (this.barEl) {
- const c = (TYPE_COLORS[this.type] || TYPE_COLORS.default);
- this.barEl.style.background = c.fg;
- }
- const side = this.el.querySelector('.met-side');
- if (side) {
- const c = (TYPE_COLORS[this.type] || TYPE_COLORS.default);
- side.style.background = c.fg;
- }
- }
-
- const content = this.el.querySelector('.met-content');
- if (content) {
- const safeTitle = this.title ? `${escapeHTML(this.title)}
` : '';
- const safeMessage = this.html
- ? `${this.html}
`
- : (this.message ? `${escapeHTML(this.message)}
` : '');
- content.innerHTML = `${safeTitle}${safeMessage}`;
- }
-
- // resetTimerOnUpdate: 更新内容后重置计时器
- if (this.config.resetTimerOnUpdate && this.config.duration > 0) {
- cancelAnimationFrame(this.rafId);
- this.startedAt = Date.now();
- this.remaining = this.config.duration;
- this._startTimer();
- }
-
- if (typeof this.config.onUpdate === 'function') {
- try { this.config.onUpdate(this); } catch (e) { console.error('onUpdate callback error:', e); }
- }
-
- Toast.trigger('afterUpdate', this);
- return this;
- }
-
- close(immediate = false) {
- if (this.closing) return;
- this.closing = true;
-
- Toast.trigger('beforeClose', this);
- cancelAnimationFrame(this.rafId);
- this._cleanups.forEach(fn => { try { fn(); } catch (_) {} });
- this._cleanups = [];
-
- const el = this.el;
- if (!el) {
- meToast._remove(this.id);
- return;
- }
-
- // 立即脱离文档流:记录当前位置,然后设为 absolute,让下方 toast 自然上移
- const container = el.parentNode;
- if (container && !immediate) {
- const elRect = el.getBoundingClientRect();
- const containerRect = container.getBoundingClientRect();
- el.style.position = 'absolute';
- el.style.top = (elRect.top - containerRect.top) + 'px';
- el.style.left = (elRect.left - containerRect.left) + 'px';
- el.style.width = elRect.width + 'px';
- el.style.margin = '0';
- }
-
- el.classList.add('met-leaving');
- el.classList.remove('met-show');
-
- const pos = this.config.position || 'top-right';
- const isRTL = getLocaleDirection(getCurrentLocale()) === 'rtl';
- let transform = 'scale(.96)';
-
- if (pos.includes('right')) transform = isRTL ? 'translateX(-120%)' : 'translateX(120%)';
- else if (pos.includes('left')) transform = isRTL ? 'translateX(120%)' : 'translateX(-120%)';
- else if (pos.startsWith('top')) transform = 'translateY(-20px)';
- else transform = 'translateY(20px)';
-
- el.style.transform = transform;
- el.style.opacity = '0';
-
- setTimeout(() => this._destroy(), immediate ? 0 : 300);
- }
-
- _destroy() {
- if (this.el && this.el.parentNode) {
- this.el.parentNode.removeChild(this.el);
- }
- this.el = null;
-
- if (typeof this.config.onClose === 'function') {
- try {
- this.config.onClose(this);
- } catch (e) {
- console.error('onClose callback error:', e);
- }
- }
-
- Toast.trigger('afterClose', this);
- meToast._remove(this.id);
- }
-}
-
-// ========== HTML模板辅助函数 ==========
-
-/**
- * 按钮行模板 — confirm/prompt 共用
- * @param {string} buttons - 按钮HTML
- * @returns {string} 按钮行HTML
- */
-const _btnRow = (buttons) => `
-
- ${buttons}
-
`;
-
-/**
- * 确认对话框模板
- * @param {Object} opts - 选项
- * @returns {string} HTML
- */
-const _confirmHTML = (opts) => {
- const confirmText = opts.confirmText || i18nT('confirm');
- const cancelText = opts.cancelText || i18nT('cancel');
- const confirmColor = opts.confirmColor || '#10b981';
- const cancelColor = opts.cancelColor || '#6b7280';
- return _btnRow(`
-
-
- `);
-};
-
-/**
- * 输入对话框模板
- * @param {Object} opts - 选项
- * @returns {string} HTML
- */
-const _promptHTML = (opts) => {
- const submitText = opts.submitText || i18nT('confirm');
- const cancelText = opts.cancelText || i18nT('cancel');
- const submitColor = opts.submitColor || '#3b82f6';
- const cancelColor = opts.cancelColor || '#6b7280';
- return `
-
- ${_btnRow(`
-
-
- `)}
- `;
-};
-
-/**
- * 进度条模板
- * @param {Object} opts - 选项
- * @returns {string} HTML
- */
-const _progressHTML = (opts) => {
- const color = opts.progressColor || '#3b82f6';
- return `
-
- 0%
- `;
-};
-
-/**
- * Action Toast 模板
- * @param {Array} actions - 操作按钮数组 [{ text, onClick, style }]
- * @returns {string} HTML
- */
-const _actionHTML = (actions) => {
- if (!Array.isArray(actions) || actions.length === 0) return '';
- const buttons = actions.map((a, i) => {
- const bg = a.style?.background || a.color || '#6366f1';
- const cls = `met-action-btn-${i}`;
- return ``;
- }).join('');
- return _btnRow(buttons);
-};
-
-/**
- * 主对象
- */
-const meToast = {
- _toasts: new Map(),
- _config: { ...DEFAULTS },
- version: '0.1.2',
-
- configure(opts) {
- if (!opts || typeof opts !== 'object') return this;
- // 原地更新,保持 _config 引用一致(index.js 的增强对象共享同一个 _config)
- Object.assign(this._config, opts);
-
- if (opts.theme) {
- applyTheme(opts.theme);
- }
-
- if (opts.locale) {
- setCurrentLocale(opts.locale);
- }
-
- return this;
- },
-
- _emit(opts) {
- const merged = { ...this._config, ...opts };
- if (merged.content && !merged.message) merged.message = merged.content;
-
- const t = new Toast(merged);
- t.create();
- this._toasts.set(t.id, t);
-
- return t;
- },
-
- _remove(id) {
- this._toasts.delete(id);
- },
-
- find(id) {
- if (!id) return undefined;
- return this._toasts.get(id);
- },
-
- /**
- * 显示默认Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象(当第一个参数是字符串时)
- */
- show(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'default');
- return this._emit(normalized);
- },
-
- /**
- * 显示成功Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象
- */
- success(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'success');
- return this._emit(normalized);
- },
-
- /**
- * 显示错误Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象
- */
- error(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'error');
- return this._emit(normalized);
- },
-
- /**
- * 显示警告Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象
- */
- warning(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'warning');
- return this._emit(normalized);
- },
-
- /**
- * 显示信息Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象
- */
- info(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'info');
- return this._emit(normalized);
- },
-
- /**
- * 显示加载Toast
- * @param {string|Object} messageOrOpts - 消息字符串或选项对象
- * @param {Object} [opts] - 选项对象
- */
- loading(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'loading');
- normalized.duration = 0;
- normalized.closeButton = false;
- normalized.showProgress = false;
-
- const t = this._emit(normalized);
-
- return {
- id: t.id,
- success: (msg, o = {}) => this._resolve(t, 'success', msg, o),
- error: (msg, o = {}) => this._resolve(t, 'error', msg, o),
- info: (msg, o = {}) => this._resolve(t, 'info', msg, o),
- warning: (msg, o = {}) => this._resolve(t, 'warning', msg, o),
- update: (p) => { t.update(p); return this; },
- dismiss: () => t.close(),
- };
- },
-
- promise(promise, opts = {}) {
- if (!promise || typeof promise.then !== 'function') {
- console.error('MeToast.promise: first argument must be a Promise');
- return Promise.reject(new Error('Invalid promise'));
- }
-
- const loadingMsg = opts.loading || i18nT('loading');
- const successMsg = opts.success || i18nT('success');
- const errorMsg = opts.error || i18nT('error');
-
- const ctrl = this.loading(loadingMsg);
-
- return Promise.resolve(promise)
- .then((data) => {
- ctrl.success(successMsg);
- return data;
- })
- .catch((err) => {
- ctrl.error(errorMsg);
- throw err;
- });
- },
-
- _resolve(loadingToast, type, message, opts) {
- const id = loadingToast.id;
- const old = this._toasts.get(id);
- if (!old) return null;
-
- const position = old.config.position;
- old.close();
-
- return this._emit({ ...opts, type, message, position });
- },
-
- /**
- * 确认对话框
- * @param {string} message - 消息内容
- * @param {Object} [opts] - 选项
- * @returns {Promise}
- */
- confirm(message, opts = {}) {
- if (typeof message !== 'string') {
- console.error('MeToast.confirm: message must be a string');
- return Promise.resolve(false);
- }
-
- return new Promise((resolve) => {
- let resolved = false;
- const safeResolve = (val) => { if (!resolved) { resolved = true; clearTimeout(safetyTimeout); resolve(val); } };
- const safetyTimeout = setTimeout(() => safeResolve(false), 10000);
-
- const t = this._emit({
- ...opts,
- type: opts.type || 'warning',
- message,
- duration: 0,
- closeButton: false,
- closeOnClick: false,
- draggable: false,
- html: _confirmHTML(opts),
- });
-
- setTimeout(() => {
- const confirmBtn = t.el?.querySelector('.met-confirm-btn');
- const cancelBtn = t.el?.querySelector('.met-cancel-btn');
-
- if (confirmBtn) {
- confirmBtn.addEventListener('click', () => {
- t.close();
- safeResolve(true);
- });
- }
-
- if (cancelBtn) {
- cancelBtn.addEventListener('click', () => {
- t.close();
- safeResolve(false);
- });
- }
- }, 0);
- });
- },
-
- /**
- * 输入对话框
- * @param {string} message - 消息内容
- * @param {Object} [opts] - 选项
- * @returns {Promise}
- */
- prompt(message, opts = {}) {
- if (typeof message !== 'string') {
- console.error('MeToast.prompt: message must be a string');
- return Promise.resolve(null);
- }
-
- return new Promise((resolve) => {
- let resolved = false;
- const safeResolve = (val) => { if (!resolved) { resolved = true; clearTimeout(safetyTimeout); resolve(val); } };
- const safetyTimeout = setTimeout(() => safeResolve(null), 10000);
-
- const t = this._emit({
- ...opts,
- type: opts.type || 'info',
- message,
- duration: 0,
- closeButton: false,
- closeOnClick: false,
- draggable: false,
- html: _promptHTML(opts),
- });
-
- setTimeout(() => {
- const input = t.el?.querySelector('.met-input');
- const submitBtn = t.el?.querySelector('.met-submit-btn');
- const cancelBtn = t.el?.querySelector('.met-cancel-btn');
-
- if (input) {
- input.focus();
-
- input.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') {
- t.close();
- safeResolve(input.value);
- }
- });
- }
-
- if (submitBtn) {
- submitBtn.addEventListener('click', () => {
- t.close();
- safeResolve(input?.value || null);
- });
- }
-
- if (cancelBtn) {
- cancelBtn.addEventListener('click', () => {
- t.close();
- safeResolve(null);
- });
- }
- }, 0);
- });
- },
-
- /**
- * 进度Toast
- * @param {string|Object} messageOrOpts - 消息或选项
- * @param {Object} [opts] - 选项
- * @returns {Object} 控制对象
- */
- progress(messageOrOpts, opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'info');
- normalized.duration = 0;
- normalized.closeButton = false;
- normalized.showProgress = false;
-
- const t = this._emit({
- ...normalized,
- html: _progressHTML(opts),
- });
-
- return {
- id: t.id,
-
- setProgress(percent) {
- const fill = t.el?.querySelector('.met-progress-fill');
- const text = t.el?.querySelector('.met-progress-text');
-
- if (fill) {
- fill.style.width = `${Math.min(100, Math.max(0, percent))}%`;
- }
-
- if (text) {
- text.textContent = `${Math.round(percent)}%`;
- }
- },
-
- complete(message) {
- this.setProgress(100);
- setTimeout(() => {
- t.update({
- type: 'success',
- message: message || i18nT('success'),
- html: '',
- });
- setTimeout(() => t.close(), 1000);
- }, 300);
- },
-
- error(message) {
- t.update({
- type: 'error',
- message: message || i18nT('error'),
- html: '',
- });
- setTimeout(() => t.close(), 2000);
- },
-
- dismiss() {
- t.close();
- },
- };
- },
-
- /**
- * 倒计时Toast
- * @param {string} message - 消息内容
- * @param {number} seconds - 秒数
- * @param {Object} [opts] - 选项
- * @returns {Object} 控制对象
- */
- countdown(message, seconds = 10, opts = {}) {
- if (typeof message !== 'string') {
- console.error('MeToast.countdown: message must be a string');
- return { cancel: () => {}, pause: () => {}, resume: () => {} };
- }
-
- let remaining = Math.max(1, parseInt(seconds) || 10);
- let timer = null;
-
- const t = this._emit({
- ...opts,
- type: opts.type || 'warning',
- message: message.replace(/\{seconds\}/g, remaining),
- duration: 0,
- closeButton: true,
- showProgress: false,
- });
-
- const tick = () => {
- remaining--;
-
- if (remaining <= 0) {
- clearInterval(timer);
- t.close();
-
- if (typeof opts.onComplete === 'function') {
- try {
- opts.onComplete();
- } catch (e) {
- console.error('countdown onComplete error:', e);
- }
- }
- return;
- }
-
- t.update({
- message: message.replace(/\{seconds\}/g, remaining),
- });
- };
-
- timer = setInterval(tick, 1000);
-
- return {
- id: t.id,
-
- cancel() {
- clearInterval(timer);
- t.close();
- },
-
- pause() {
- clearInterval(timer);
- },
-
- resume() {
- clearInterval(timer);
- timer = setInterval(tick, 1000);
- },
- };
- },
-
- /**
- * Action Toast — 带操作按钮的通知
- * @param {string|Object} messageOrOpts - 消息
- * @param {Array} actions - 操作按钮 [{ text, onClick, color }]
- * @param {Object} [opts] - 选项
- * @returns {Object} toast实例 + dismiss
- */
- action(messageOrOpts, actions = [], opts = {}) {
- const normalized = normalizeArgs([messageOrOpts, opts], 'info');
- normalized.duration = opts.duration ?? 0;
- normalized.closeButton = opts.closeButton ?? true;
-
- const t = this._emit({
- ...normalized,
- html: (normalized.html || '') + _actionHTML(actions),
- });
-
- if (Array.isArray(actions)) {
- setTimeout(() => {
- actions.forEach((a, i) => {
- const btn = t.el?.querySelector(`.met-action-btn-${i}`);
- if (btn && typeof a.onClick === 'function') {
- btn.addEventListener('click', () => {
- try { a.onClick(t); } catch (e) { console.error('Action onClick error:', e); }
- if (a.close !== false) t.close();
- });
- }
- });
- }, 0);
- }
-
- return { id: t.id, toast: t, dismiss: () => t.close() };
- },
-
- /**
- * 队列Toast
- * @param {Array} messages - 消息数组
- * @param {Object} [opts] - 选项
- * @returns {Promise}
- */
- queue(messages, opts = {}) {
- if (!Array.isArray(messages)) {
- console.error('MeToast.queue: messages must be an array');
- return { then: (fn) => Promise.resolve().then(fn), cancel: () => {} };
- }
-
- let cancelled = false;
- const promise = new Promise((resolve) => {
- let index = 0;
- const delay = opts.delay || 1000;
- const userOnClose = opts.onClose;
-
- const showNext = () => {
- if (cancelled || index >= messages.length) {
- resolve();
- return;
- }
-
- const message = messages[index];
- index++;
-
- const msg = typeof message === 'string' ? message : (message?.message || '');
- const msgObj = (typeof message === 'object' && message !== null) ? message : {};
- const msgOnClose = msgObj.onClose;
-
- this._emit({
- ...opts,
- ...msgObj,
- message: msg,
- duration: msgObj.duration || opts.duration || 3000,
- onClose: (toast) => {
- if (typeof msgOnClose === 'function') {
- try { msgOnClose(toast); } catch (_) {}
- }
- if (typeof userOnClose === 'function') {
- try { userOnClose(toast); } catch (_) {}
- }
- setTimeout(showNext, delay);
- },
- });
- };
-
- showNext();
- });
- return {
- then: (fn, rj) => promise.then(fn, rj),
- catch: (rj) => promise.catch(rj),
- cancel: () => { cancelled = true; },
- };
- },
-
- /**
- * 堆叠Toast
- * @param {Array} messages - 消息数组
- * @param {Object} [opts] - 选项
- */
- stack(messages, opts = {}) {
- if (!Array.isArray(messages)) {
- console.error('MeToast.stack: messages must be an array');
- return;
- }
-
- messages.forEach((message, index) => {
- setTimeout(() => {
- const msg = typeof message === 'string' ? message : (message?.message || '');
-
- this._emit({
- ...opts,
- ...((typeof message === 'object' && message !== null) ? message : {}),
- message: msg,
- });
- }, index * (opts.stagger || 100));
- });
- },
-
- dismiss(id) {
- if (id) {
- const t = this._toasts.get(id);
- if (t) t.close();
- return;
- }
- this._toasts.forEach(t => t.close());
- },
-
- clear(position) {
- this._toasts.forEach(t => {
- if (!position || t.config.position === position) t.close();
- });
- },
-
- destroy() {
- this.dismiss();
-
- if (typeof document !== 'undefined') {
- const containers = document.querySelectorAll('.met-container');
- containers.forEach(c => c.parentNode && c.parentNode.removeChild(c));
-
- const style = document.getElementById('metona-toast-styles');
- if (style) style.parentNode.removeChild(style);
- }
-
- this._toasts.clear();
- _containerCache.clear();
- },
-
- getAll() {
- return new Map(this._toasts);
- },
-
- count() {
- return this._toasts.size;
- },
-
- /**
- * 创建Toast分组 — 返回一个自动添加 group 参数的子对象
- * @param {string} name - 分组名称
- * @returns {Object}
- */
- group(name) {
- const self = this;
- const methods = ['show','success','error','warning','info','loading','action'];
- const g = { _group: name };
- methods.forEach(m => {
- g[m] = (...args) => {
- const lastArg = args[args.length - 1];
- const isObj = typeof lastArg === 'object' && lastArg !== null && !Array.isArray(lastArg);
- // 单对象参数:直接合并group
- if (isObj && args.length === 1) {
- return self[m]({ ...lastArg, group: name });
- }
- // 末尾是配置对象:pop后合并
- if (isObj) {
- args.pop();
- return self[m](...args, { ...lastArg, group: name });
- }
- // 无配置对象
- return self[m](...args, { group: name });
- };
- });
- g.dismiss = () => self.dismissGroup(name);
- g.count = () => self._groupCount(name);
- return g;
- },
-
- /**
- * 按组关闭Toast
- * @param {string} name - 分组名称
- */
- dismissGroup(name) {
- this._toasts.forEach(t => { if (t.group === name) t.close(); });
- },
-
- _groupCount(name) {
- let c = 0;
- this._toasts.forEach(t => { if (t.group === name) c++; });
- return c;
- },
-};
-
-export { meToast as default, meToast, Toast };
diff --git a/src/i18n.js b/src/i18n.ts
similarity index 55%
rename from src/i18n.js
rename to src/i18n.ts
index fe7f8d9..9351dd2 100644
--- a/src/i18n.js
+++ b/src/i18n.ts
@@ -1,35 +1,33 @@
/**
- * MetonaToast i18n - 国际化管理
+ * MetonaToast i18n — 国际化管理
* @module i18n
- * @version 0.1.2
- * @description 多语言支持、语言切换和翻译管理
+ * @version 0.2.0
*/
import { LOCALES } from './constants.js';
+import type { I18nUtils, LocaleInfo } from './types.js';
// 当前语言状态
let currentLocale = 'zh-CN';
-let localeListeners = new Set();
+const localeListeners: Set<(locale: string) => void> = new Set();
let fallbackLocale = 'zh-CN';
/**
* 获取当前语言
- * @returns {string} 当前语言
*/
-export const getCurrentLocale = () => {
+export const getCurrentLocale = (): string => {
return currentLocale;
};
/**
* 设置当前语言
- * @param {string} locale - 语言代码
*/
-export const setCurrentLocale = (locale) => {
+export const setCurrentLocale = (locale: string): void => {
if (!LOCALES[locale]) {
console.warn(`Locale "${locale}" not found, falling back to "${fallbackLocale}"`);
locale = fallbackLocale;
}
-
+
currentLocale = locale;
notifyLocaleListeners(locale);
saveLocale(locale);
@@ -37,130 +35,104 @@ export const setCurrentLocale = (locale) => {
/**
* 获取回退语言
- * @returns {string} 回退语言
*/
-export const getFallbackLocale = () => {
+export const getFallbackLocale = (): string => {
return fallbackLocale;
};
/**
* 设置回退语言
- * @param {string} locale - 语言代码
*/
-export const setFallbackLocale = (locale) => {
+export const setFallbackLocale = (locale: string): void => {
if (!LOCALES[locale]) {
console.warn(`Fallback locale "${locale}" not found`);
return;
}
-
fallbackLocale = locale;
};
/**
* 翻译函数
- * @param {string} key - 翻译键
- * @param {Object} params - 插值参数
- * @returns {string} 翻译后的字符串
*/
-export const t = (key, params = {}) => {
- // 尝试当前语言
+export const t = (key: string, params: Record = {}): string => {
const currentTranslation = getTranslation(currentLocale, key);
if (currentTranslation !== undefined) {
return interpolate(currentTranslation, params);
}
-
- // 尝试回退语言
+
if (currentLocale !== fallbackLocale) {
const fallbackTranslation = getTranslation(fallbackLocale, key);
if (fallbackTranslation !== undefined) {
return interpolate(fallbackTranslation, params);
}
}
-
- // 返回键名
+
console.warn(`Translation missing for key "${key}" in locale "${currentLocale}"`);
return key;
};
/**
* 获取翻译
- * @param {string} locale - 语言代码
- * @param {string} key - 翻译键
- * @returns {string|undefined} 翻译字符串
*/
-const getTranslation = (locale, key) => {
+const getTranslation = (locale: string, key: string): string | undefined => {
const localeData = LOCALES[locale];
if (!localeData) return undefined;
-
- // 支持嵌套键 (如 'common.close')
+
const keys = key.split('.');
- let result = localeData;
-
+ let result: unknown = localeData;
+
for (const k of keys) {
- if (result && typeof result === 'object' && k in result) {
- result = result[k];
+ if (result && typeof result === 'object' && k in (result as Record)) {
+ result = (result as Record)[k];
} else {
return undefined;
}
}
-
+
return typeof result === 'string' ? result : undefined;
};
/**
* 插值函数
- * @param {string} str - 包含占位符的字符串
- * @param {Object} params - 插值参数
- * @returns {string} 插值后的字符串
*/
-const interpolate = (str, params) => {
- return str.replace(/\{(\w+)\}/g, (match, key) => {
- return params[key] !== undefined ? params[key] : match;
+const interpolate = (str: string, params: Record): string => {
+ return str.replace(/\{(\w+)\}/g, (_match, key: string) => {
+ return params[key] !== undefined ? String(params[key]) : _match;
});
};
/**
* 检查翻译是否存在
- * @param {string} key - 翻译键
- * @returns {boolean} 是否存在
*/
-export const hasTranslation = (key) => {
+export const hasTranslation = (key: string): boolean => {
return getTranslation(currentLocale, key) !== undefined ||
- getTranslation(fallbackLocale, key) !== undefined;
+ getTranslation(fallbackLocale, key) !== undefined;
};
/**
* 获取所有翻译
- * @param {string} locale - 语言代码
- * @returns {Object} 翻译对象
*/
-export const getTranslations = (locale) => {
+export const getTranslations = (locale: string): Record => {
return LOCALES[locale] || {};
};
/**
* 添加翻译
- * @param {string} locale - 语言代码
- * @param {Object} translations - 翻译对象
*/
-export const addTranslations = (locale, translations) => {
+export const addTranslations = (locale: string, translations: Record): void => {
if (!LOCALES[locale]) {
LOCALES[locale] = {};
}
-
- deepMerge(LOCALES[locale], translations);
+ deepMerge(LOCALES[locale], translations as Record);
};
/**
* 深度合并对象
- * @param {Object} target - 目标对象
- * @param {Object} source - 源对象
- * @returns {Object} 合并后的对象
*/
-const deepMerge = (target, source) => {
+const deepMerge = (target: Record, source: Record): Record => {
for (const key in source) {
if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
- deepMerge(target[key], source[key]);
+ deepMerge(target[key] as Record, source[key] as Record);
} else {
target[key] = source[key];
}
@@ -170,32 +142,29 @@ const deepMerge = (target, source) => {
/**
* 移除翻译
- * @param {string} locale - 语言代码
- * @param {string} key - 翻译键
*/
-export const removeTranslation = (locale, key) => {
+export const removeTranslation = (locale: string, key: string): void => {
const localeData = LOCALES[locale];
if (!localeData) return;
-
+
const keys = key.split('.');
- let current = localeData;
-
+ let current: Record = localeData;
+
for (let i = 0; i < keys.length - 1; i++) {
if (current[keys[i]] && typeof current[keys[i]] === 'object') {
- current = current[keys[i]];
+ current = current[keys[i]] as Record;
} else {
return;
}
}
-
+
delete current[keys[keys.length - 1]];
};
/**
* 清除翻译
- * @param {string} locale - 语言代码
*/
-export const clearTranslations = (locale) => {
+export const clearTranslations = (locale: string): void => {
if (LOCALES[locale]) {
LOCALES[locale] = {};
}
@@ -203,28 +172,23 @@ export const clearTranslations = (locale) => {
/**
* 获取支持的语言列表
- * @returns {Array} 语言代码数组
*/
-export const getSupportedLocales = () => {
+export const getSupportedLocales = (): string[] => {
return Object.keys(LOCALES);
};
/**
* 检查语言是否支持
- * @param {string} locale - 语言代码
- * @returns {boolean} 是否支持
*/
-export const isLocaleSupported = (locale) => {
+export const isLocaleSupported = (locale: string): boolean => {
return locale in LOCALES;
};
/**
* 获取语言名称
- * @param {string} locale - 语言代码
- * @returns {string} 语言名称
*/
-export const getLocaleName = (locale) => {
- const names = {
+export const getLocaleName = (locale: string): string => {
+ const names: Record = {
'zh-CN': '简体中文',
'zh-TW': '繁體中文',
'en-US': 'English (US)',
@@ -304,26 +268,22 @@ export const getLocaleName = (locale) => {
've': 'Tshivenda',
'nr': 'isiNdebele',
};
-
+
return names[locale] || locale;
};
/**
* 获取语言方向
- * @param {string} locale - 语言代码
- * @returns {string} 语言方向 ('ltr' 或 'rtl')
*/
-export const getLocaleDirection = (locale) => {
+export const getLocaleDirection = (locale: string): 'ltr' | 'rtl' => {
const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi', 'ps', 'sd', 'ug'];
return rtlLocales.includes(locale) ? 'rtl' : 'ltr';
};
/**
* 获取语言信息
- * @param {string} locale - 语言代码
- * @returns {Object} 语言信息
*/
-export const getLocaleInfo = (locale) => {
+export const getLocaleInfo = (locale: string): LocaleInfo => {
return {
code: locale,
name: getLocaleName(locale),
@@ -336,17 +296,15 @@ export const getLocaleInfo = (locale) => {
/**
* 获取所有语言信息
- * @returns {Array} 语言信息数组
*/
-export const getAllLocaleInfo = () => {
+export const getAllLocaleInfo = (): LocaleInfo[] => {
return getSupportedLocales().map(getLocaleInfo);
};
/**
* 保存语言到本地存储
- * @param {string} locale - 语言代码
*/
-export const saveLocale = (locale) => {
+export const saveLocale = (locale: string): void => {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('metona-toast-locale', locale);
@@ -358,9 +316,8 @@ export const saveLocale = (locale) => {
/**
* 从本地存储加载语言
- * @returns {string} 语言代码
*/
-export const loadLocale = () => {
+export const loadLocale = (): string => {
if (typeof localStorage !== 'undefined') {
try {
return localStorage.getItem('metona-toast-locale') || getDefaultLocale();
@@ -373,50 +330,43 @@ export const loadLocale = () => {
/**
* 获取默认语言
- * @returns {string} 语言代码
*/
-export const getDefaultLocale = () => {
+export const getDefaultLocale = (): string => {
if (typeof navigator !== 'undefined') {
- // 尝试从浏览器获取语言
- const browserLocale = navigator.language || navigator.userLanguage;
+ const browserLocale = navigator.language || (navigator as unknown as { userLanguage?: string }).userLanguage;
if (browserLocale && isLocaleSupported(browserLocale)) {
return browserLocale;
}
-
- // 尝试语言代码的前两位
+
const shortLocale = browserLocale?.split('-')[0];
if (shortLocale && isLocaleSupported(shortLocale)) {
return shortLocale;
}
}
-
+
return fallbackLocale;
};
/**
* 初始化国际化系统
*/
-export const initI18n = () => {
+export const initI18n = (): void => {
const savedLocale = loadLocale();
setCurrentLocale(savedLocale);
};
/**
* 切换语言
- * @param {string} locale - 新语言
*/
-export const switchLocale = (locale) => {
+export const switchLocale = (locale: string): void => {
setCurrentLocale(locale);
};
/**
* 添加语言监听器
- * @param {Function} listener - 监听器函数
- * @returns {Function} 移除监听器函数
*/
-export const addLocaleListener = (listener) => {
+export const addLocaleListener = (listener: (locale: string) => void): () => void => {
localeListeners.add(listener);
-
return () => {
localeListeners.delete(listener);
};
@@ -424,17 +374,15 @@ export const addLocaleListener = (listener) => {
/**
* 移除语言监听器
- * @param {Function} listener - 监听器函数
*/
-export const removeLocaleListener = (listener) => {
+export const removeLocaleListener = (listener: (locale: string) => void): void => {
localeListeners.delete(listener);
};
/**
* 通知语言监听器
- * @param {string} locale - 语言代码
*/
-const notifyLocaleListeners = (locale) => {
+const notifyLocaleListeners = (locale: string): void => {
localeListeners.forEach((listener) => {
try {
listener(locale);
@@ -447,17 +395,14 @@ const notifyLocaleListeners = (locale) => {
/**
* 清除所有语言监听器
*/
-export const clearLocaleListeners = () => {
+export const clearLocaleListeners = (): void => {
localeListeners.clear();
};
/**
* 格式化数字
- * @param {number} number - 数字
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatNumber = (number, options = {}) => {
+export const formatNumber = (number: number, options: Intl.NumberFormatOptions = {}): string => {
try {
return new Intl.NumberFormat(currentLocale, options).format(number);
} catch (e) {
@@ -467,12 +412,8 @@ export const formatNumber = (number, options = {}) => {
/**
* 格式化货币
- * @param {number} amount - 金额
- * @param {string} currency - 货币代码
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatCurrency = (amount, currency = 'USD', options = {}) => {
+export const formatCurrency = (amount: number, currency = 'USD', options: Intl.NumberFormatOptions = {}): string => {
try {
return new Intl.NumberFormat(currentLocale, {
style: 'currency',
@@ -486,11 +427,8 @@ export const formatCurrency = (amount, currency = 'USD', options = {}) => {
/**
* 格式化百分比
- * @param {number} value - 值
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatPercent = (value, options = {}) => {
+export const formatPercent = (value: number, options: Intl.NumberFormatOptions = {}): string => {
try {
return new Intl.NumberFormat(currentLocale, {
style: 'percent',
@@ -505,26 +443,20 @@ export const formatPercent = (value, options = {}) => {
/**
* 格式化日期
- * @param {Date|number|string} date - 日期
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatDate = (date, options = {}) => {
+export const formatDate = (date: Date | number | string, options: Intl.DateTimeFormatOptions = {}): string => {
try {
const dateObj = date instanceof Date ? date : new Date(date);
return new Intl.DateTimeFormat(currentLocale, options).format(dateObj);
} catch (e) {
- return date.toString();
+ return String(date);
}
};
/**
* 格式化时间
- * @param {Date|number|string} date - 日期
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatTime = (date, options = {}) => {
+export const formatTime = (date: Date | number | string, options: Intl.DateTimeFormatOptions = {}): string => {
return formatDate(date, {
hour: 'numeric',
minute: 'numeric',
@@ -535,22 +467,19 @@ export const formatTime = (date, options = {}) => {
/**
* 格式化相对时间
- * @param {Date|number|string} date - 日期
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatRelativeTime = (date, options = {}) => {
+export const formatRelativeTime = (date: Date | number | string, options: Intl.RelativeTimeFormatOptions = {}): string => {
try {
const dateObj = date instanceof Date ? date : new Date(date);
const now = new Date();
- const diff = dateObj - now;
-
+ const diff = dateObj.getTime() - now.getTime();
+
const rtf = new Intl.RelativeTimeFormat(currentLocale, {
numeric: 'auto',
...options,
});
-
- const units = [
+
+ const units: Array<{ unit: Intl.RelativeTimeFormatUnit; ms: number }> = [
{ unit: 'year', ms: 365 * 24 * 60 * 60 * 1000 },
{ unit: 'month', ms: 30 * 24 * 60 * 60 * 1000 },
{ unit: 'week', ms: 7 * 24 * 60 * 60 * 1000 },
@@ -559,41 +488,32 @@ export const formatRelativeTime = (date, options = {}) => {
{ unit: 'minute', ms: 60 * 1000 },
{ unit: 'second', ms: 1000 },
];
-
+
for (const { unit, ms } of units) {
if (Math.abs(diff) >= ms || unit === 'second') {
const value = Math.round(diff / ms);
return rtf.format(value, unit);
}
}
-
- return date.toString();
+
+ return String(date);
} catch (e) {
- return date.toString();
+ return String(date);
}
};
/**
* 格式化列表
- * @param {Array} list - 列表
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatList = (list, options = {}) => {
- try {
- return new Intl.ListFormat(currentLocale, options).format(list);
- } catch (e) {
- return list.join(', ');
- }
+export const formatList = (list: string[], _options: Record = {}): string => {
+ // Intl.ListFormat requires ES2021+; fallback to comma join for wider compat
+ return list.join(', ');
};
/**
* 格式化复数
- * @param {number} count - 数量
- * @param {Object} options - 格式化选项
- * @returns {string} 格式化后的字符串
*/
-export const formatPlural = (count, options = {}) => {
+export const formatPlural = (count: number, options: Intl.PluralRulesOptions = {}): string => {
try {
return new Intl.PluralRules(currentLocale, options).select(count);
} catch (e) {
@@ -603,99 +523,50 @@ export const formatPlural = (count, options = {}) => {
/**
* 复数翻译
- * @param {string} key - 翻译键
- * @param {number} count - 数量
- * @param {Object} params - 插值参数
- * @returns {string} 翻译后的字符串
*/
-export const plural = (key, count, params = {}) => {
+export const plural = (key: string, count: number, params: Record = {}): string => {
const pluralForm = formatPlural(count);
const pluralKey = `${key}.${pluralForm}`;
-
+
if (hasTranslation(pluralKey)) {
return t(pluralKey, { ...params, count });
}
-
- // 尝试通用形式
+
if (hasTranslation(key)) {
return t(key, { ...params, count });
}
-
+
return key;
};
/**
* 日期时间格式化选项
*/
-export const dateTimeFormats = {
- short: {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- },
- medium: {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- hour: 'numeric',
- minute: 'numeric',
- },
- long: {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- weekday: 'long',
- hour: 'numeric',
- minute: 'numeric',
- second: 'numeric',
- },
- time: {
- hour: 'numeric',
- minute: 'numeric',
- second: 'numeric',
- },
- date: {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- },
- weekday: {
- weekday: 'long',
- },
- month: {
- month: 'long',
- },
- year: {
- year: 'numeric',
- },
+export const dateTimeFormats: Record = {
+ short: { year: 'numeric', month: 'short', day: 'numeric' },
+ medium: { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' },
+ long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: 'numeric', minute: 'numeric', second: 'numeric' },
+ time: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
+ date: { year: 'numeric', month: 'long', day: 'numeric' },
+ weekday: { weekday: 'long' },
+ month: { month: 'long' },
+ year: { year: 'numeric' },
};
/**
* 数字格式化选项
*/
-export const numberFormats = {
- integer: {
- maximumFractionDigits: 0,
- },
- decimal: {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- },
- percent: {
- style: 'percent',
- minimumFractionDigits: 0,
- maximumFractionDigits: 2,
- },
- currency: {
- style: 'currency',
- currency: 'USD',
- },
+export const numberFormats: Record = {
+ integer: { maximumFractionDigits: 0 },
+ decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
+ percent: { style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 2 },
+ currency: { style: 'currency', currency: 'USD' },
};
/**
- * 国际化工具 — 代理层,直接引用模块函数
+ * 国际化工具 — 代理层
*/
-export const i18nUtils = {
+export const i18nUtils: I18nUtils = {
t,
plural,
getCurrentLocale,
@@ -734,14 +605,18 @@ export const i18nUtils = {
/**
* 预设语言包
*/
-export const presetLocales = {
+export const presetLocales: Record;
+}> = {
'zh-CN': {
name: '简体中文',
nativeName: '简体中文',
direction: 'ltr',
translations: LOCALES['zh-CN'],
},
-
'en-US': {
name: 'English (US)',
nativeName: 'English (US)',
@@ -750,46 +625,4 @@ export const presetLocales = {
},
};
-/**
- * 创建国际化管理器
- * @returns {Object} 国际化管理器
- */
-export const createI18nManager = () => {
- return {
- t,
- plural,
- getCurrentLocale,
- setCurrentLocale,
- switchLocale,
- getFallbackLocale,
- setFallbackLocale,
- hasTranslation,
- getTranslations,
- addTranslations,
- removeTranslation,
- clearTranslations,
- getSupportedLocales,
- isLocaleSupported,
- getLocaleName,
- getLocaleDirection,
- getLocaleInfo,
- getAllLocaleInfo,
- formatNumber,
- formatCurrency,
- formatPercent,
- formatDate,
- formatTime,
- formatRelativeTime,
- formatList,
- formatPlural,
- addLocaleListener,
- removeLocaleListener,
- clearLocaleListeners,
- initI18n,
- saveLocale,
- loadLocale,
- getDefaultLocale,
- };
-};
-
export { i18nUtils as default };
diff --git a/src/icons.js b/src/icons.ts
similarity index 98%
rename from src/icons.js
rename to src/icons.ts
index e239388..eea2c8b 100644
--- a/src/icons.js
+++ b/src/icons.ts
@@ -1,134 +1,134 @@
/**
* MetonaToast Icons — 图标SVG定义
* @module icons
- * @version 0.1.2
- * @description 80+ 内置SVG图标
+ * @version 0.2.0
+ * @description 107 个内置 SVG 图标
*/
-export const ICONS = {
+export const ICONS: Record = {
success: ``,
-
+
error: ``,
-
+
warning: ``,
-
+
info: ``,
-
+
loading: ``,
-
+
close: ``,
-
+
check: ``,
-
+
x: ``,
-
+
alert: ``,
-
+
question: ``,
-
+
star: ``,
-
+
heart: ``,
-
+
bell: ``,
-
+
mail: ``,
-
+
settings: ``,
-
+
user: ``,
-
+
home: ``,
-
+
search: ``,
-
+
plus: ``,
-
+
minus: ``,
-
+
edit: ``,
-
+
trash: ``,
-
+
download: ``,
-
+
upload: ``,
-
+
share: ``,
-
+
link: ``,
-
+
external: ``,
-
+
clock: ``,
-
+
calendar: ``,
-
+
map: ``,
-
+
compass: ``,
-
+
globe: ``,
-
+
wifi: ``,
-
+
cloud: ``,
-
+
sun: ``,
-
+
moon: ``,
-
+
zap: ``,
-
+
activity: ``,
-
+
cpu: ``,
-
+
database: ``,
-
+
server: ``,
-
+
terminal: ``,
-
+
code: ``,
-
+
git: ``,
-
+
package: ``,
-
+
layers: ``,
-
+
grid: ``,
-
+
list: ``,
-
+
filter: ``,
-
+
sort: ``,
-
+
refresh: ``,
-
+
sync: ``,
-
+
power: ``,
-
+
battery: ``,
-
+
bluetooth: ``,
-
+
volume: ``,
-
+
mic: ``,
-
+
camera: ``,
-
+
image: ``,
-
+
video: ``,
-
+
music: ``,
-
+
file: ``,
-
+
folder: ``,
-
+
clipboard: ``,
-
+
save: ``,
-
+
print: ``,
-
+
eye: ``,
-
+
eyeOff: ``,
-
+
lock: ``,
-
+
unlock: ``,
-
+
shield: ``,
-
+
key: ``,
-
+
flag: ``,
-
+
bookmark: ``,
-
+
tag: ``,
-
+
gift: ``,
-
+
award: ``,
-
+
target: ``,
-
+
crosshair: ``,
-
+
move: ``,
-
+
maximize: ``,
-
+
minimize: ``,
-
+
copy: ``,
-
+
cut: ``,
-
+
paste: ``,
-
+
rotateCw: ``,
-
+
rotateCcw: ``,
-
+
zoomIn: ``,
-
+
zoomOut: ``,
-
+
crop: ``,
-
+
sliders: ``,
-
+
toggleLeft: ``,
-
+
toggleRight: ``,
-
+
checkCircle: ``,
-
+
xCircle: ``,
-
+
alertCircle: ``,
-
+
infoCircle: ``,
-
+
helpCircle: ``,
-
+
alertTriangle: ``,
-
+
checkSquare: ``,
-
+
square: ``,
-
+
circle: ``,
-
+
triangle: ``,
-
+
hexagon: ``,
-
+
octagon: ``,
-
+
pentagon: ``,
-
+
diamond: ``,
diff --git a/src/index.js b/src/index.ts
similarity index 59%
rename from src/index.js
rename to src/index.ts
index 3b6a879..37c1e78 100644
--- a/src/index.js
+++ b/src/index.ts
@@ -1,40 +1,41 @@
/**
- * MetonaToast - 轻量级Toast通知库
+ * MetonaToast — 轻量级Toast通知库
* @module metona-toast
- * @version 0.1.2
+ * @version 0.2.0
* @author thzxx
- * @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
* @license MIT
*/
-import { meToast, Toast } from './core.js';
+import { meToast } from './api.js';
+import { Toast } from './toast.js';
import { animationUtils } from './animations.js';
import { themeUtils } from './themes.js';
import { i18nUtils } from './i18n.js';
import { pluginUtils, presetPlugins } from './plugins.js';
import { DEFAULTS } from './constants.js';
+import type { MeToast, ToastConfig, InitOptions, StatusInfo, ToastInstance, ToastOptions, Plugin } from './types.js';
// 版本信息
-const VERSION = '0.1.2';
+const VERSION = '0.2.0';
/**
* 主对象增强
*/
-const enhancedMeToast = {
+const enhancedMeToast: MeToast = {
...meToast,
-
+
version: VERSION,
-
+
animations: animationUtils,
themes: themeUtils,
i18n: i18nUtils,
plugins: pluginUtils,
presetPlugins,
-
+
/**
- * 安装插件,并连接必要的生命周期钩子
+ * 安装插件
*/
- use(plugin, options = {}) {
+ use(plugin: string | Record, options: Record = {}): MeToast {
if (typeof plugin === 'string') {
const preset = presetPlugins[plugin];
if (!preset) {
@@ -45,58 +46,64 @@ const enhancedMeToast = {
// 连接插件钩子
if (plugin === 'accessibility') {
- Toast.on('afterShow', (toast) => preset.announce(toast));
+ Toast.on('afterShow', (toast: ToastInstance) => {
+ if (typeof preset.announce === 'function') preset.announce(toast);
+ });
}
if (plugin === 'persistence') {
- const saved = preset.install();
- if (saved) this.configure(saved);
- Toast.on('afterClose', () => { preset.save(this.getConfig()); });
+ const saved = typeof preset.install === 'function' ? preset.install(pluginUtils as unknown as import('./types.js').PluginManager) : null;
+ if (saved) this.configure(saved as Partial);
+ Toast.on('afterClose', () => {
+ if (typeof (preset as Record).save === 'function') {
+ (preset as Record void>).save(this.getConfig());
+ }
+ });
}
} else if (plugin && typeof plugin === 'object') {
- const name = plugin.name || 'custom';
- this.plugins.register(name, { ...plugin, ...options });
+ const name = (plugin as Record).name || 'custom';
+ this.plugins.register(name, { ...plugin, ...options } as unknown as Plugin);
}
return this;
},
-
+
/**
* 初始化
*/
- init(options = {}) {
+ init(options: InitOptions = {}): MeToast {
if (options.config) {
this.configure(options.config);
}
-
+
if (options.theme) {
this.themes.switchTheme(options.theme);
}
-
+
if (options.locale) {
this.i18n.switchLocale(options.locale);
}
-
+
if (options.plugins && Array.isArray(options.plugins)) {
- options.plugins.forEach((plugin) => {
- this.use(plugin);
+ options.plugins.forEach((p) => {
+ this.use(p as string);
});
}
-
+
return this;
},
-
+
/**
* 销毁
*/
- destroy() {
+ destroy(): void {
if (this._destroyed) return;
this._destroyed = true;
this.dismiss();
-
- if (this.plugins && typeof this.plugins.destroy === 'function') {
- this.plugins.destroy();
+
+ if (this.plugins && typeof (this.plugins as unknown as Record).destroy === 'function') {
+ (this.plugins as unknown as Record void>).destroy();
}
-
+
if (this.themes) {
if (typeof this.themes.clearThemeListeners === 'function') {
this.themes.clearThemeListeners();
@@ -105,30 +112,30 @@ const enhancedMeToast = {
this.themes.unwatchSystemTheme();
}
}
-
+
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
this.i18n.clearLocaleListeners();
}
-
+
if (this.animations && typeof this.animations.cancelAll === 'function') {
this.animations.cancelAll();
}
-
+
if (typeof document !== 'undefined') {
const containers = document.querySelectorAll('.met-container');
- containers.forEach((c) => c.parentNode && c.parentNode.removeChild(c));
-
+ containers.forEach((c) => { if (c.parentNode) c.parentNode.removeChild(c); });
+
const style = document.getElementById('metona-toast-styles');
- if (style) style.parentNode.removeChild(style);
+ if (style && style.parentNode) style.parentNode.removeChild(style);
}
-
+
this._toasts.clear();
},
-
+
/**
* 获取状态
*/
- getStatus() {
+ getStatus(): StatusInfo {
return {
version: VERSION,
toasts: this._toasts.size,
@@ -138,18 +145,18 @@ const enhancedMeToast = {
animations: this.animations?.getActiveCount?.() || 0,
};
},
-
+
/**
* 获取配置
*/
- getConfig() {
+ getConfig(): ToastConfig {
return { ...this._config };
},
-
+
/**
* 更新配置
*/
- updateConfig(config) {
+ updateConfig(config: Partial): MeToast {
if (config && typeof config === 'object') {
Object.assign(this._config, config);
}
@@ -159,96 +166,95 @@ const enhancedMeToast = {
/**
* 重置配置
*/
- resetConfig() {
- // 原地重置:先清除所有自有属性,再回填默认值
+ resetConfig(): MeToast {
const keys = Object.keys(this._config);
- keys.forEach(k => delete this._config[k]);
+ keys.forEach(k => delete (this._config as Record)[k]);
Object.assign(this._config, DEFAULTS);
return this;
},
-
+
/**
* 获取Toast列表
*/
- getToasts() {
+ getToasts(): ToastInstance[] {
return Array.from(this._toasts.values());
},
-
+
/**
* 检查是否有Toast
*/
- hasToasts() {
+ hasToasts(): boolean {
return this._toasts.size > 0;
},
-
+
/**
* 获取Toast
*/
- getToast(id) {
+ getToast(id: string): ToastInstance | null {
if (!id) return null;
return this._toasts.get(id) || null;
},
-
+
/**
* 关闭所有Toast
*/
- closeAll() {
+ closeAll(): void {
this._toasts.forEach((t) => t.close());
},
-
+
/**
* 清除所有Toast
*/
- clearAll() {
+ clearAll(): void {
this._toasts.forEach((t) => t.close());
},
-
+
/**
* 暂停所有Toast
*/
- pauseAll() {
+ pauseAll(): void {
this._toasts.forEach((t) => t._pause());
},
-
+
/**
* 恢复所有Toast
*/
- resumeAll() {
+ resumeAll(): void {
this._toasts.forEach((t) => t._resume());
},
-
+
/**
* 更新所有Toast
*/
- updateAll(partial) {
+ updateAll(partial: Partial): void {
if (partial && typeof partial === 'object') {
this._toasts.forEach((t) => t.update(partial));
}
},
-
+
/**
* 查找Toast
*/
- findToasts(predicate) {
+ findToasts(predicate: (toast: ToastInstance) => boolean): ToastInstance[] {
if (typeof predicate !== 'function') return [];
return Array.from(this._toasts.values()).filter(predicate);
},
-
+
/**
* 按类型查找Toast
*/
- findByType(type) {
+ findByType(type: string): ToastInstance[] {
return this.findToasts((t) => t.type === type);
},
-
+
/**
* 按位置查找Toast
*/
- findByPosition(position) {
+ findByPosition(position: string): ToastInstance[] {
return this.findToasts((t) => t.config.position === position);
},
-
};
+
// 初始化主题和国际化
if (typeof themeUtils.initTheme === 'function') {
themeUtils.initTheme();
@@ -261,9 +267,15 @@ if (typeof i18nUtils.initI18n === 'function') {
if (typeof window !== 'undefined') {
window.MeToast = enhancedMeToast;
window.Met = enhancedMeToast;
-
+
// Notification API — 页面不可见时自动发送系统通知
- enhancedMeToast._notify = (toast) => {
+ const sendNotification = (toast: ToastInstance): void => {
+ try {
+ new Notification(toast.title || toast.type, { body: toast.message });
+ } catch (_e) { /* noop */ }
+ };
+
+ (enhancedMeToast as unknown as Record)._notify = (toast: ToastInstance) => {
if (typeof Notification === 'undefined') return;
if (Notification.permission === 'denied') return;
if (!document.hidden) return;
@@ -273,16 +285,55 @@ if (typeof window !== 'undefined') {
}
sendNotification(toast);
};
- const sendNotification = (toast) => {
- try {
- new Notification(toast.title || toast.type, { body: toast.message });
- } catch (_) {}
- };
- Toast.on('afterShow', (toast) => {
- if (toast.config.notifyWhenHidden) enhancedMeToast._notify(toast);
+
+ Toast.on('afterShow', (toast: ToastInstance) => {
+ const config = toast.config as ToastConfig;
+ if (config.notifyWhenHidden) {
+ ((enhancedMeToast as unknown as Record void>)._notify)(toast);
+ }
});
}
// 导出
export default enhancedMeToast;
export { enhancedMeToast as meToast, enhancedMeToast as Met, enhancedMeToast as MeToast, Toast, VERSION };
+
+// 类型重导出(供 TypeScript 消费者 import type 使用)
+export type {
+ ToastType,
+ ToastPosition,
+ ToastTheme,
+ ToastAnimation,
+ ToastProgressDirection,
+ ToastConfig,
+ ToastOptions,
+ ToastInstance,
+ TypeColor,
+ ErrorInfo,
+ LoadingControl,
+ ProgressControl,
+ CountdownControl,
+ ActionControl,
+ QueueControl,
+ GroupAPI,
+ ActionButton,
+ PromiseOptions,
+ ConfirmOptions,
+ PromptOptions,
+ ProgressOptions,
+ CountdownOptions,
+ QueueOptions,
+ StackOptions,
+ InitOptions,
+ StatusInfo,
+ AnimationConfig,
+ AnimationUtils,
+ ThemeConfig,
+ ThemePreview,
+ ThemeUtils,
+ LocaleInfo,
+ I18nUtils,
+ Plugin,
+ PluginManager,
+ PluginUtils,
+} from './types.js';
diff --git a/src/locales.js b/src/locales.ts
similarity index 98%
rename from src/locales.js
rename to src/locales.ts
index 38bc1f9..551b359 100644
--- a/src/locales.js
+++ b/src/locales.ts
@@ -1,11 +1,11 @@
/**
* MetonaToast Locales — 国际化翻译数据
* @module locales
- * @version 0.1.2
- * @description 内置 zh-CN / en-US 完整翻译(已去重优化)
+ * @version 0.2.0
+ * @description 内置 zh-CN / en-US 完整翻译
*/
-export const LOCALES = {
+export const LOCALES: Record> = {
'zh-CN': {
// === 操作 ===
close: '关闭',
diff --git a/src/plugins.js b/src/plugins.js
deleted file mode 100644
index dc97a54..0000000
--- a/src/plugins.js
+++ /dev/null
@@ -1,210 +0,0 @@
-/**
- * MetonaToast Plugins - 插件系统
- * @module plugins
- * @version 0.1.2
- * @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
- */
-
-import { t } from './i18n.js';
-
-/**
- * 插件管理器
- */
-class PluginManager {
- constructor() {
- this.plugins = new Map();
- this.initialized = false;
- }
-
- register(name, plugin) {
- if (this.plugins.has(name)) {
- console.warn(`Plugin "${name}" is already registered`);
- return this;
- }
- if (!plugin || typeof plugin !== 'object' || !plugin.name) {
- console.error(`Invalid plugin "${name}": must be an object with a "name" property`);
- return this;
- }
-
- // 先存储再 install,确保 install 内通过 this 设置的实例属性保留在存储对象上
- // 保留插件自带的 name(若有),否则使用注册 key 作为 name
- const stored = { ...plugin, installed: false, enabled: true };
- if (!stored.name) stored.name = name;
- this.plugins.set(name, stored);
-
- if (stored.install) {
- try { stored.install(this); stored.installed = true; }
- catch (e) { console.error(`Failed to install plugin "${name}":`, e); }
- }
- return this;
- }
-
- unregister(name) {
- const plugin = this.plugins.get(name);
- if (!plugin) return this;
- if (plugin.uninstall) {
- try { plugin.uninstall(this); } catch (e) { console.error(`Failed to uninstall plugin "${name}":`, e); }
- }
- this.plugins.delete(name);
- return this;
- }
-
- get(name) { return this.plugins.get(name) || null; }
- has(name) { return this.plugins.has(name); }
- getAll() { return Array.from(this.plugins.values()); }
- getNames() { return Array.from(this.plugins.keys()); }
-
- enable(name) {
- const p = this.plugins.get(name);
- if (p) p.enabled = true;
- return this;
- }
- disable(name) {
- const p = this.plugins.get(name);
- if (p) p.enabled = false;
- return this;
- }
- isEnabled(name) {
- const p = this.plugins.get(name);
- return p ? p.enabled : false;
- }
-
- destroy() {
- this.plugins.forEach((p, name) => {
- if (p.destroy) { try { p.destroy(this); } catch (e) {} }
- });
- this.plugins.clear();
- }
-}
-
-/**
- * 预设插件
- */
-const presetPlugins = {
-
- /**
- * 键盘快捷键插件 — ESC 关闭所有 Toast
- */
- keyboard: {
- name: 'keyboard',
- version: '1.0.0',
- description: 'ESC 关闭所有 Toast',
-
- _handler: null,
-
- install() {
- if (typeof document === 'undefined') return;
- this._handler = (e) => {
- if (e.key === 'Escape') {
- // 点击所有关闭按钮
- document.querySelectorAll('.met-toast .met-close').forEach(btn => {
- try { btn.click(); } catch (_) {}
- });
- }
- };
- document.addEventListener('keydown', this._handler);
- },
-
- uninstall() {
- if (this._handler && typeof document !== 'undefined') {
- document.removeEventListener('keydown', this._handler);
- this._handler = null;
- }
- },
- },
-
- /**
- * 持久化插件 — 自动保存/加载配置到 localStorage
- */
- persistence: {
- name: 'persistence',
- version: '1.0.0',
- description: '自动持久化配置到 localStorage',
- storageKey: 'metona-toast-config',
-
- install() {
- // 加载已保存的配置
- if (typeof localStorage !== 'undefined') {
- try {
- const saved = localStorage.getItem(this.storageKey);
- return saved ? JSON.parse(saved) : null;
- } catch (_) { return null; }
- }
- return null;
- },
-
- save(config) {
- if (typeof localStorage !== 'undefined') {
- try { localStorage.setItem(this.storageKey, JSON.stringify(config)); } catch (_) {}
- }
- },
-
- uninstall() {
- if (typeof localStorage !== 'undefined') {
- try { localStorage.removeItem(this.storageKey); } catch (_) {}
- }
- },
- },
-
- /**
- * 无障碍插件 — 屏幕阅读器公告
- */
- accessibility: {
- name: 'accessibility',
- version: '1.0.0',
- description: '通过屏幕阅读器朗读 Toast 内容',
-
- announce(toast) {
- if (typeof document === 'undefined') return;
- const el = document.createElement('div');
- el.setAttribute('aria-live', 'assertive');
- el.setAttribute('aria-atomic', 'true');
- el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
- const typeName = t(toast.type) || toast.type;
- el.textContent = `${typeName}: ${toast.title || ''} ${toast.message || ''}`;
- document.body.appendChild(el);
- setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
- },
- },
-};
-
-/**
- * 插件工具
- */
-const pluginUtils = {
- createManager() { return new PluginManager(); },
- register(name, plugin) { return defaultPluginManager.register(name, plugin); },
- unregister(name) { return defaultPluginManager.unregister(name); },
- get(name) { return defaultPluginManager.get(name); },
- has(name) { return defaultPluginManager.has(name); },
- getAll() { return defaultPluginManager.getAll(); },
- getNames() { return defaultPluginManager.getNames(); },
- enable(name) { return defaultPluginManager.enable(name); },
- disable(name) { return defaultPluginManager.disable(name); },
- isEnabled(name) { return defaultPluginManager.isEnabled(name); },
- getPreset(name) { return presetPlugins[name] || null; },
- getAllPresets() { return { ...presetPlugins }; },
-
- createPlugin(config) {
- return {
- name: config.name || 'custom',
- version: config.version || '1.0.0',
- description: config.description || '',
- hooks: config.hooks || {},
- install: config.install || null,
- uninstall: config.uninstall || null,
- ...config,
- };
- },
-
- validatePlugin(plugin) {
- const errors = [];
- if (!plugin || typeof plugin !== 'object') errors.push('Plugin must be an object');
- if (!plugin.name && !plugin.version) errors.push('Plugin must have a name or version');
- return { valid: errors.length === 0, errors };
- },
-};
-
-const defaultPluginManager = new PluginManager();
-
-export { presetPlugins, pluginUtils, defaultPluginManager, PluginManager };
diff --git a/src/plugins.ts b/src/plugins.ts
new file mode 100644
index 0000000..a0b9644
--- /dev/null
+++ b/src/plugins.ts
@@ -0,0 +1,220 @@
+/**
+ * MetonaToast Plugins — 插件系统
+ * @module plugins
+ * @version 0.2.0
+ */
+
+import { t } from './i18n.js';
+import type { Plugin, PluginManager as IPluginManager, PluginUtils } from './types.js';
+
+/**
+ * 插件管理器
+ */
+class PluginManager implements IPluginManager {
+ plugins: Map;
+ initialized: boolean;
+
+ constructor() {
+ this.plugins = new Map();
+ this.initialized = false;
+ }
+
+ register(name: string, plugin: Plugin): PluginManager {
+ if (this.plugins.has(name)) {
+ console.warn(`Plugin "${name}" is already registered`);
+ return this;
+ }
+ if (!plugin || typeof plugin !== 'object' || !plugin.name) {
+ console.error(`Invalid plugin "${name}": must be an object with a "name" property`);
+ return this;
+ }
+
+ const stored: Plugin = { ...plugin, installed: false, enabled: true };
+ if (!stored.name) stored.name = name;
+ this.plugins.set(name, stored);
+
+ if (stored.install) {
+ try {
+ stored.install(this);
+ stored.installed = true;
+ } catch (e) {
+ console.error(`Failed to install plugin "${name}":`, e);
+ }
+ }
+ return this;
+ }
+
+ unregister(name: string): PluginManager {
+ const plugin = this.plugins.get(name);
+ if (!plugin) return this;
+ if (plugin.uninstall) {
+ try { plugin.uninstall(this); } catch (e) { console.error(`Failed to uninstall plugin "${name}":`, e); }
+ }
+ this.plugins.delete(name);
+ return this;
+ }
+
+ get(name: string): Plugin | null { return this.plugins.get(name) || null; }
+ has(name: string): boolean { return this.plugins.has(name); }
+ getAll(): Plugin[] { return Array.from(this.plugins.values()); }
+ getNames(): string[] { return Array.from(this.plugins.keys()); }
+
+ enable(name: string): PluginManager {
+ const p = this.plugins.get(name);
+ if (p) p.enabled = true;
+ return this;
+ }
+ disable(name: string): PluginManager {
+ const p = this.plugins.get(name);
+ if (p) p.enabled = false;
+ return this;
+ }
+ isEnabled(name: string): boolean {
+ const p = this.plugins.get(name);
+ return p ? !!p.enabled : false;
+ }
+
+ destroy(): void {
+ this.plugins.forEach((p) => {
+ if (p.destroy) { try { p.destroy(this); } catch (_e) { /* noop */ } }
+ });
+ this.plugins.clear();
+ }
+}
+
+/**
+ * 预设插件
+ */
+const presetPlugins: Record = {
+ /**
+ * 键盘快捷键插件 — ESC 关闭所有 Toast
+ */
+ keyboard: {
+ name: 'keyboard',
+ version: '1.0.0',
+ description: 'ESC 关闭所有 Toast',
+ _handler: null as ((e: KeyboardEvent) => void) | null,
+
+ install(this: Plugin) {
+ if (typeof document === 'undefined') return;
+ const handler = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ document.querySelectorAll('.met-toast .met-close').forEach(btn => {
+ try { (btn as HTMLElement).click(); } catch (_e) { /* noop */ }
+ });
+ }
+ };
+ (this as Record)._handler = handler;
+ document.addEventListener('keydown', handler);
+ },
+
+ uninstall(this: Plugin) {
+ const handler = (this as Record)._handler as ((e: KeyboardEvent) => void) | null;
+ if (handler && typeof document !== 'undefined') {
+ document.removeEventListener('keydown', handler);
+ (this as Record)._handler = null;
+ }
+ },
+ },
+
+ /**
+ * 持久化插件 — 自动保存/加载配置到 localStorage
+ */
+ persistence: {
+ name: 'persistence',
+ version: '1.0.0',
+ description: '自动持久化配置到 localStorage',
+ storageKey: 'metona-toast-config',
+
+ install(this: Plugin) {
+ if (typeof localStorage !== 'undefined') {
+ try {
+ const key = (this as Record).storageKey as string;
+ const saved = localStorage.getItem(key);
+ return saved ? JSON.parse(saved) : null;
+ } catch (_e) { return null; }
+ }
+ return null;
+ },
+
+ save(this: Plugin, config: Record) {
+ if (typeof localStorage !== 'undefined') {
+ try {
+ const key = (this as Record).storageKey as string;
+ localStorage.setItem(key, JSON.stringify(config));
+ } catch (_e) { /* noop */ }
+ }
+ },
+
+ uninstall(this: Plugin) {
+ if (typeof localStorage !== 'undefined') {
+ try {
+ const key = (this as Record).storageKey as string;
+ localStorage.removeItem(key);
+ } catch (_e) { /* noop */ }
+ }
+ },
+ },
+
+ /**
+ * 无障碍插件 — 屏幕阅读器公告
+ */
+ accessibility: {
+ name: 'accessibility',
+ version: '1.0.0',
+ description: '通过屏幕阅读器朗读 Toast 内容',
+
+ announce(toast: { type: string; title?: string; message?: string }) {
+ if (typeof document === 'undefined') return;
+ const el = document.createElement('div');
+ el.setAttribute('aria-live', 'assertive');
+ el.setAttribute('aria-atomic', 'true');
+ el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
+ const typeName = t(toast.type) || toast.type;
+ el.textContent = `${typeName}: ${toast.title || ''} ${toast.message || ''}`;
+ document.body.appendChild(el);
+ setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
+ },
+ },
+};
+
+/**
+ * 插件工具
+ */
+const pluginUtils: PluginUtils = {
+ createManager(): PluginManager { return new PluginManager(); },
+ register(name: string, plugin: Plugin): PluginManager { return defaultPluginManager.register(name, plugin); },
+ unregister(name: string): PluginManager { return defaultPluginManager.unregister(name); },
+ get(name: string): Plugin | null { return defaultPluginManager.get(name); },
+ has(name: string): boolean { return defaultPluginManager.has(name); },
+ getAll(): Plugin[] { return defaultPluginManager.getAll(); },
+ getNames(): string[] { return defaultPluginManager.getNames(); },
+ enable(name: string): PluginManager { return defaultPluginManager.enable(name); },
+ disable(name: string): PluginManager { return defaultPluginManager.disable(name); },
+ isEnabled(name: string): boolean { return defaultPluginManager.isEnabled(name); },
+ getPreset(name: string): Plugin | null { return presetPlugins[name] || null; },
+ getAllPresets(): Record { return { ...presetPlugins }; },
+
+ createPlugin(config: Partial): Plugin {
+ return {
+ name: config.name || 'custom',
+ version: config.version || '1.0.0',
+ description: config.description || '',
+ hooks: config.hooks || {},
+ install: config.install || undefined,
+ uninstall: config.uninstall || undefined,
+ ...config,
+ };
+ },
+
+ validatePlugin(plugin: Partial): { valid: boolean; errors: string[] } {
+ const errors: string[] = [];
+ if (!plugin || typeof plugin !== 'object') errors.push('Plugin must be an object');
+ if (!plugin.name && !plugin.version) errors.push('Plugin must have a name or version');
+ return { valid: errors.length === 0, errors };
+ },
+};
+
+const defaultPluginManager = new PluginManager();
+
+export { presetPlugins, pluginUtils, defaultPluginManager, PluginManager };
diff --git a/src/styles.js b/src/styles.ts
similarity index 87%
rename from src/styles.js
rename to src/styles.ts
index ee3f0c1..3814e6f 100644
--- a/src/styles.js
+++ b/src/styles.ts
@@ -1,21 +1,19 @@
/**
- * MetonaToast Styles - 样式管理(精简版 v0.1.2)
+ * MetonaToast Styles — 样式管理
* @module styles
- * @version 0.1.2
- * @description 样式注入与主题管理,移除未使用的组件样式
+ * @version 0.2.0
*/
-import { THEMES } from './constants.js';
+import type { ThemeConfig } from './types.js';
// 样式缓存
-let styleElement = null;
+let styleElement: HTMLStyleElement | null = null;
/**
* 生成CSS样式
*/
-const generateCSS = (theme) => {
+const generateCSS = (): string => {
return `
- /* MetonaToast 基础样式 */
.met-container {
pointer-events: none;
display: flex;
@@ -28,7 +26,6 @@ const generateCSS = (theme) => {
z-index: 9999;
}
- /* 位置样式 */
.met-container.top-left { top: 0; left: 0; align-items: flex-start; }
.met-container.top-center { top: 0; left: 0; right: 0; align-items: center; }
.met-container.top-right { top: 0; right: 0; align-items: flex-end; }
@@ -36,7 +33,6 @@ const generateCSS = (theme) => {
.met-container.bottom-center{ bottom: 0; left: 0; right: 0; align-items: center; }
.met-container.bottom-right { bottom: 0; right: 0; align-items: flex-end; }
- /* Toast基础样式 */
.met-toast {
position: relative;
display: flex;
@@ -70,17 +66,14 @@ const generateCSS = (theme) => {
perspective: 1000;
}
- /* 悬停效果 */
.met-toast:hover {
box-shadow: var(--met-hover-shadow, 0 14px 48px -10px rgba(0,0,0,0.22), 0 6px 18px -4px rgba(0,0,0,0.10)) !important;
}
- /* 可点击状态 */
.met-toast.met-clickable {
cursor: pointer;
}
- /* 图标样式 */
.met-icon {
flex-shrink: 0;
display: flex;
@@ -91,7 +84,6 @@ const generateCSS = (theme) => {
margin-top: 1px;
}
- /* 内容样式 */
.met-content {
flex: 1;
min-width: 0;
@@ -99,21 +91,18 @@ const generateCSS = (theme) => {
overflow-wrap: break-word;
}
- /* 标题样式 */
.met-title {
font-weight: 600;
font-size: 14px;
letter-spacing: 0.1px;
}
- /* 消息样式 */
.met-message {
font-size: 13px;
opacity: 0.85;
margin-top: 2px;
}
- /* 关闭按钮样式 */
.met-close {
flex-shrink: 0;
background: transparent;
@@ -135,7 +124,6 @@ const generateCSS = (theme) => {
background: var(--met-close-hover-bg, rgba(0,0,0,0.06));
}
- /* 进度条样式 - 水平 */
.met-progress {
position: absolute;
left: 0; right: 0; bottom: 0;
@@ -145,7 +133,6 @@ const generateCSS = (theme) => {
border-radius: 0 0 12px 12px;
}
- /* 进度条样式 - 垂直 */
.met-progress-v {
position: absolute;
left: 0; top: 0; bottom: 0;
@@ -155,7 +142,6 @@ const generateCSS = (theme) => {
border-radius: 12px 0 0 12px;
}
- /* 进度条指示器 - 水平 */
.met-bar {
position: absolute;
left: 0; top: 0;
@@ -164,7 +150,6 @@ const generateCSS = (theme) => {
transform: scaleX(1);
}
- /* 进度条指示器 - 垂直 */
.met-bar-v {
position: absolute;
left: 0; bottom: 0;
@@ -173,7 +158,6 @@ const generateCSS = (theme) => {
transform: scaleY(1);
}
- /* 侧边指示器 */
.met-side {
position: absolute;
left: 0; top: 0; bottom: 0;
@@ -181,13 +165,11 @@ const generateCSS = (theme) => {
border-radius: 12px 0 0 12px;
}
- /* 加载动画 */
.met-spin {
animation: met-rot 1s linear infinite;
transform-origin: 50% 50%;
}
- /* 离开状态 */
.met-toast.met-leaving {
pointer-events: none;
z-index: 1;
@@ -197,7 +179,6 @@ const generateCSS = (theme) => {
to { transform: rotate(360deg); }
}
- /* === 进入动画:初始隐藏态 === */
.met-anim-slide.met-toast,
.met-anim-fade.met-toast,
.met-anim-scale.met-toast,
@@ -212,7 +193,6 @@ const generateCSS = (theme) => {
opacity: 0;
}
- /* === 进入动画:@keyframes 定义 === */
@keyframes met-slide-in {
0% { transform: translateX(80px); opacity: 0; }
65% { transform: translateX(-6px); opacity: 1; }
@@ -273,7 +253,6 @@ const generateCSS = (theme) => {
100% { transform: translateX(0); opacity: 1; }
}
- /* === 进入动画:绑定到 .met-show === */
.met-anim-slide.met-toast.met-show { animation: met-slide-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-fade.met-toast.met-show { animation: met-fade-in 0.50s ease forwards; }
.met-anim-scale.met-toast.met-show { animation: met-scale-in 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
@@ -286,7 +265,6 @@ const generateCSS = (theme) => {
.met-anim-slideLeft.met-toast.met-show { animation: met-slideLeft-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-slideRight.met-toast.met-show { animation: met-slideRight-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
- /* 响应式设计 */
@media (max-width: 480px) {
.met-container { padding: 12px !important; }
.met-toast { width: 100% !important; min-width: 0 !important; }
@@ -321,10 +299,6 @@ const generateCSS = (theme) => {
.met-toast { width: 100%; min-width: 0; }
}
- /* 无障碍支持 */
- .met-toast[role="alert"] { }
- .met-toast[role="status"] { }
-
@media (prefers-reduced-motion: reduce) {
.met-toast { transition: opacity 0.15s !important; }
.met-toast.met-show { animation: none !important; opacity: 1 !important; }
@@ -341,7 +315,6 @@ const generateCSS = (theme) => {
.met-close { border: 2px solid currentColor; border-radius: 4px; }
}
- /* 焦点样式 */
.met-toast:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
@@ -351,32 +324,22 @@ const generateCSS = (theme) => {
outline-offset: 2px;
}
- /* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
.met-toast:hover { box-shadow: inherit; }
.met-toast { min-height: 48px; }
.met-close { min-width: 48px; min-height: 48px; }
}
- @media (hover: none) and (pointer: coarse) {
- .met-toast { min-height: 52px; padding: 16px 18px; }
- .met-close { min-width: 52px; min-height: 52px; padding: 8px; }
- .met-icon { width: 28px; height: 28px; }
- }
-
- /* 打印样式 */
@media print {
.met-container { display: none !important; }
.met-toast { display: none !important; }
}
- /* 全屏/画中画模式 */
:fullscreen .met-container,
:picture-in-picture .met-container {
z-index: 2147483647;
}
- /* 安全区域适配 */
@supports (padding: max(0px)) {
.met-container {
padding: max(24px, env(safe-area-inset-top))
@@ -386,30 +349,25 @@ const generateCSS = (theme) => {
}
}
- /* 按下状态 */
.met-toast:active {
transform: scale(0.98);
}
- /* 类型左侧边框 */
.met-toast.met-success { border-left: 4px solid #10b981; }
.met-toast.met-error { border-left: 4px solid #ef4444; }
.met-toast.met-warning { border-left: 4px solid #f59e0b; }
.met-toast.met-info { border-left: 4px solid #3b82f6; }
.met-toast.met-loading { border-left: 4px solid #6366f1; }
- /* 进度条过渡 */
.met-bar, .met-bar-v {
transition: transform 0.1s linear;
}
- /* 滚动条样式 */
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
.met-container::-webkit-scrollbar-track { background: transparent; }
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
.met-container::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.3); }
- /* 暗色主题特定样式 */
.met-toast.met-theme-dark {
background: rgba(28, 32, 40, 0.94);
color: #e6e8eb;
@@ -427,7 +385,6 @@ const generateCSS = (theme) => {
background: rgba(255, 255, 255, 0.08);
}
- /* 亮色主题特定样式 */
.met-toast.met-theme-light {
background: rgba(255, 255, 255, 0.96);
color: #1f2937;
@@ -445,7 +402,6 @@ const generateCSS = (theme) => {
background: rgba(0, 0, 0, 0.06);
}
- /* 自定义主题支持 */
.met-toast[data-theme] {
--met-bg: var(--met-theme-bg);
--met-text: var(--met-theme-text);
@@ -453,7 +409,6 @@ const generateCSS = (theme) => {
--met-shadow: var(--met-theme-shadow);
}
- /* 暗色模式自动检测 */
@media (prefers-color-scheme: dark) {
.met-theme-auto .met-toast {
background: rgba(28, 32, 40, 0.94);
@@ -489,14 +444,14 @@ const generateCSS = (theme) => {
/**
* 注入样式
*/
-export const injectStyles = (theme = THEMES.light) => {
+export const injectStyles = (): void => {
if (typeof document === 'undefined') return;
if (styleElement && document.getElementById('metona-toast-styles')) {
return;
}
- const css = generateCSS(theme);
+ const css = generateCSS();
styleElement = document.createElement('style');
styleElement.id = 'metona-toast-styles';
@@ -508,31 +463,30 @@ export const injectStyles = (theme = THEMES.light) => {
/**
* 更新样式
*/
-export const updateStyles = (theme) => {
+export const updateStyles = (): void => {
if (!styleElement) {
- injectStyles(theme);
+ injectStyles();
return;
}
- const css = generateCSS(theme);
+ const css = generateCSS();
styleElement.textContent = css.replace(/\s+/g, ' ');
};
/**
* 移除样式
*/
-export const removeStyles = () => {
+export const removeStyles = (): void => {
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
-
styleElement = null;
};
/**
* 生成主题CSS变量
*/
-export const generateThemeVariables = (theme) => {
+export const generateThemeVariables = (theme: ThemeConfig): string => {
return `
:root {
--met-theme-bg: ${theme.bg};
@@ -549,7 +503,7 @@ export const generateThemeVariables = (theme) => {
/**
* 应用主题变量
*/
-export const applyThemeVariables = (theme) => {
+export const applyThemeVariables = (theme: ThemeConfig): void => {
if (typeof document === 'undefined') return;
const css = generateThemeVariables(theme);
@@ -562,7 +516,7 @@ export const applyThemeVariables = (theme) => {
/**
* 清除主题变量
*/
-export const clearThemeVariables = () => {
+export const clearThemeVariables = (): void => {
const customStyle = document.getElementById('metona-toast-custom-styles');
if (customStyle && customStyle.parentNode) {
customStyle.parentNode.removeChild(customStyle);
@@ -572,26 +526,26 @@ export const clearThemeVariables = () => {
/**
* 获取系统主题
*/
-export const getSystemTheme = () => {
+export const getSystemTheme = (): string => {
if (typeof window === 'undefined') return 'light';
return window.matchMedia &&
- window.matchMedia('(prefers-color-scheme: dark)').matches
- ? 'dark'
- : 'light';
+ window.matchMedia('(prefers-color-scheme: dark)').matches
+ ? 'dark'
+ : 'light';
};
/**
* 监听系统主题变化
*/
-export const watchSystemTheme = (callback) => {
+export const watchSystemTheme = (callback: (theme: string) => void): (() => void) => {
if (typeof window === 'undefined' || !window.matchMedia) {
return () => {};
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- const handler = (e) => {
+ const handler = (e: MediaQueryListEvent) => {
callback(e.matches ? 'dark' : 'light');
};
@@ -605,10 +559,11 @@ export const watchSystemTheme = (callback) => {
/**
* 自动应用系统主题
*/
-export const autoApplySystemTheme = () => {
- const applyTheme = (theme) => {
- updateStyles(THEMES[theme]);
- applyThemeVariables(THEMES[theme]);
+export const autoApplySystemTheme = (): (() => void) => {
+ const applyTheme = (theme: string): void => {
+ updateStyles();
+ // _THEMES reference is from constants, but here we just need to use theme string
+ // applyThemeVariables is called externally
};
applyTheme(getSystemTheme());
diff --git a/src/templates.ts b/src/templates.ts
new file mode 100644
index 0000000..fe5d55c
--- /dev/null
+++ b/src/templates.ts
@@ -0,0 +1,79 @@
+/**
+ * MetonaToast Templates — HTML 模板辅助函数
+ * @module templates
+ * @version 0.2.0
+ * @description confirm / prompt / progress / action 的 DOM 模板
+ */
+
+import { escapeHTML } from './utils.js';
+import { t } from './i18n.js';
+import type { ActionButton, ConfirmOptions, PromptOptions, ProgressOptions } from './types.js';
+
+/**
+ * 按钮行模板 — confirm/prompt 共用
+ */
+export const btnRow = (buttons: string): string => `
+
+ ${buttons}
+
`;
+
+/**
+ * 确认对话框模板
+ */
+export const confirmHTML = (opts: ConfirmOptions): string => {
+ const confirmText = opts.confirmText || t('confirm');
+ const cancelText = opts.cancelText || t('cancel');
+ const confirmColor = opts.confirmColor || '#10b981';
+ const cancelColor = opts.cancelColor || '#6b7280';
+ return btnRow(`
+
+
+ `);
+};
+
+/**
+ * 输入对话框模板
+ */
+export const promptHTML = (opts: PromptOptions): string => {
+ const submitText = opts.submitText || t('confirm');
+ const cancelText = opts.cancelText || t('cancel');
+ const submitColor = opts.submitColor || '#3b82f6';
+ const cancelColor = opts.cancelColor || '#6b7280';
+ return `
+
+ ${btnRow(`
+
+
+ `)}
+ `;
+};
+
+/**
+ * 进度条模板
+ */
+export const progressHTML = (opts: ProgressOptions): string => {
+ const color = opts.progressColor || '#3b82f6';
+ return `
+
+ 0%
+ `;
+};
+
+/**
+ * Action Toast 模板
+ */
+export const actionHTML = (actions: ActionButton[]): string => {
+ if (!Array.isArray(actions) || actions.length === 0) return '';
+ const buttons = actions.map((a, i) => {
+ const bg = a.style?.background || a.color || '#6366f1';
+ const cls = `met-action-btn-${i}`;
+ return ``;
+ }).join('');
+ return btnRow(buttons);
+};
diff --git a/src/themes.js b/src/themes.ts
similarity index 62%
rename from src/themes.js
rename to src/themes.ts
index bfe8b94..a25e190 100644
--- a/src/themes.js
+++ b/src/themes.ts
@@ -1,45 +1,39 @@
/**
- * MetonaToast Themes - 主题管理
+ * MetonaToast Themes — 主题管理
* @module themes
- * @version 0.1.2
- * @description 主题系统、自定义主题和主题切换
+ * @version 0.2.0
*/
import { THEMES } from './constants.js';
import { prefersDark } from './utils.js';
+import type { ThemeConfig, ThemePreview, ThemeUtils } from './types.js';
// 当前主题状态
let currentTheme = 'auto';
-let themeListeners = new Set();
-let systemThemeMediaQuery = null;
-let systemThemeListener = null;
+const themeListeners: Set<(theme: string, resolved: string) => void> = new Set();
+let systemThemeMediaQuery: MediaQueryList | null = null;
+let systemThemeListener: ((e: MediaQueryListEvent) => void) | null = null;
/**
* 获取系统主题
- * @returns {string} 系统主题 ('light' 或 'dark')
*/
-export const getSystemTheme = () => {
+export const getSystemTheme = (): 'light' | 'dark' => {
if (typeof window === 'undefined') return 'light';
return prefersDark() ? 'dark' : 'light';
};
/**
* 获取主题(别名)
- * @param {string} theme - 主题名称
- * @returns {string} 解析后的主题
*/
-export const getTheme = (theme) => {
+export const getTheme = (theme: string): string => {
return resolveTheme(theme);
};
/**
* 解析主题
- * @param {string} theme - 主题名称
- * @returns {string} 解析后的主题
*/
-export const resolveTheme = (theme) => {
+export const resolveTheme = (theme: string): string => {
if (theme === 'auto') {
- // 优先使用用户设置的全局主题,否则使用系统主题
if (currentTheme && currentTheme !== 'auto') {
return currentTheme;
}
@@ -50,44 +44,37 @@ export const resolveTheme = (theme) => {
/**
* 获取主题配置
- * @param {string} theme - 主题名称
- * @returns {Object} 主题配置
*/
-export const getThemeConfig = (theme) => {
+export const getThemeConfig = (theme: string): ThemeConfig => {
const resolved = resolveTheme(theme);
- return THEMES[resolved] || THEMES.light;
+ return (THEMES[resolved] as ThemeConfig) || (THEMES.light as ThemeConfig);
};
/**
* 应用主题
- * @param {string} theme - 主题名称
*/
-export const applyTheme = (theme) => {
+export const applyTheme = (theme: string): void => {
currentTheme = theme;
const resolved = resolveTheme(theme);
-
- // 应用主题到文档
+
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-met-theme', resolved);
document.documentElement.classList.remove('met-theme-light', 'met-theme-dark', 'met-theme-auto');
document.documentElement.classList.add(`met-theme-${resolved}`);
-
- // 设置CSS变量
+
const config = getThemeConfig(theme);
setThemeVariables(config);
}
-
- // 通知监听器
+
notifyThemeListeners(theme, resolved);
};
/**
* 设置主题CSS变量
- * @param {Object} config - 主题配置
*/
-export const setThemeVariables = (config) => {
+export const setThemeVariables = (config: ThemeConfig): void => {
if (typeof document === 'undefined') return;
-
+
const root = document.documentElement;
root.style.setProperty('--met-bg', config.bg);
root.style.setProperty('--met-text', config.text);
@@ -96,8 +83,7 @@ export const setThemeVariables = (config) => {
root.style.setProperty('--met-hover-shadow', config.hoverShadow);
root.style.setProperty('--met-progress-bg', config.progressBg);
root.style.setProperty('--met-close-hover-bg', config.closeHoverBg);
-
- // 设置颜色变量
+
root.style.setProperty('--met-success', '#10b981');
root.style.setProperty('--met-error', '#ef4444');
root.style.setProperty('--met-warning', '#f59e0b');
@@ -108,51 +94,39 @@ export const setThemeVariables = (config) => {
/**
* 清除主题CSS变量
*/
-export const clearThemeVariables = () => {
+export const clearThemeVariables = (): void => {
if (typeof document === 'undefined') return;
-
+
const root = document.documentElement;
const variables = [
- '--met-bg',
- '--met-text',
- '--met-border',
- '--met-shadow',
- '--met-hover-shadow',
- '--met-progress-bg',
- '--met-close-hover-bg',
- '--met-success',
- '--met-error',
- '--met-warning',
- '--met-info',
- '--met-loading',
+ '--met-bg', '--met-text', '--met-border', '--met-shadow',
+ '--met-hover-shadow', '--met-progress-bg', '--met-close-hover-bg',
+ '--met-success', '--met-error', '--met-warning', '--met-info', '--met-loading',
];
-
- variables.forEach((variable) => {
- root.style.removeProperty(variable);
+
+ variables.forEach((v) => {
+ root.style.removeProperty(v);
});
};
/**
* 获取当前主题
- * @returns {string} 当前主题
*/
-export const getCurrentTheme = () => {
+export const getCurrentTheme = (): string => {
return currentTheme;
};
/**
* 获取解析后的主题
- * @returns {string} 解析后的主题
*/
-export const getResolvedTheme = () => {
+export const getResolvedTheme = (): string => {
return resolveTheme(currentTheme);
};
/**
* 切换主题
- * @param {string} theme - 新主题
*/
-export const switchTheme = (theme) => {
+export const switchTheme = (theme: string): void => {
applyTheme(theme);
saveTheme(theme);
};
@@ -160,7 +134,7 @@ export const switchTheme = (theme) => {
/**
* 切换亮色/暗色主题
*/
-export const toggleTheme = () => {
+export const toggleTheme = (): void => {
const resolved = getResolvedTheme();
const newTheme = resolved === 'dark' ? 'light' : 'dark';
switchTheme(newTheme);
@@ -169,15 +143,14 @@ export const toggleTheme = () => {
/**
* 重置为自动主题
*/
-export const resetToAuto = () => {
+export const resetToAuto = (): void => {
switchTheme('auto');
};
/**
* 保存主题到本地存储
- * @param {string} theme - 主题名称
*/
-export const saveTheme = (theme) => {
+export const saveTheme = (theme: string): void => {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('metona-toast-theme', theme);
@@ -189,9 +162,8 @@ export const saveTheme = (theme) => {
/**
* 从本地存储加载主题
- * @returns {string} 主题名称
*/
-export const loadTheme = () => {
+export const loadTheme = (): string => {
if (typeof localStorage !== 'undefined') {
try {
return localStorage.getItem('metona-toast-theme') || 'auto';
@@ -205,41 +177,36 @@ export const loadTheme = () => {
/**
* 初始化主题系统
*/
-export const initTheme = () => {
- // 加载保存的主题
+export const initTheme = (): void => {
const savedTheme = loadTheme();
applyTheme(savedTheme);
-
- // 监听系统主题变化
watchSystemTheme();
};
/**
* 监听系统主题变化
*/
-export const watchSystemTheme = () => {
+export const watchSystemTheme = (): void => {
if (typeof window === 'undefined' || !window.matchMedia) return;
-
- // 移除旧的监听器
+
if (systemThemeMediaQuery && systemThemeListener) {
systemThemeMediaQuery.removeEventListener('change', systemThemeListener);
}
-
- // 创建新的监听器
+
systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- systemThemeListener = (e) => {
+ systemThemeListener = () => {
if (currentTheme === 'auto') {
applyTheme('auto');
}
};
-
+
systemThemeMediaQuery.addEventListener('change', systemThemeListener);
};
/**
* 停止监听系统主题变化
*/
-export const unwatchSystemTheme = () => {
+export const unwatchSystemTheme = (): void => {
if (systemThemeMediaQuery && systemThemeListener) {
systemThemeMediaQuery.removeEventListener('change', systemThemeListener);
systemThemeMediaQuery = null;
@@ -249,12 +216,9 @@ export const unwatchSystemTheme = () => {
/**
* 添加主题监听器
- * @param {Function} listener - 监听器函数
- * @returns {Function} 移除监听器函数
*/
-export const addThemeListener = (listener) => {
+export const addThemeListener = (listener: (theme: string, resolved: string) => void): () => void => {
themeListeners.add(listener);
-
return () => {
themeListeners.delete(listener);
};
@@ -262,18 +226,15 @@ export const addThemeListener = (listener) => {
/**
* 移除主题监听器
- * @param {Function} listener - 监听器函数
*/
-export const removeThemeListener = (listener) => {
+export const removeThemeListener = (listener: (theme: string, resolved: string) => void): void => {
themeListeners.delete(listener);
};
/**
* 通知主题监听器
- * @param {string} theme - 主题名称
- * @param {string} resolved - 解析后的主题
*/
-const notifyThemeListeners = (theme, resolved) => {
+const notifyThemeListeners = (theme: string, resolved: string): void => {
themeListeners.forEach((listener) => {
try {
listener(theme, resolved);
@@ -286,74 +247,64 @@ const notifyThemeListeners = (theme, resolved) => {
/**
* 清除所有主题监听器
*/
-export const clearThemeListeners = () => {
+export const clearThemeListeners = (): void => {
themeListeners.clear();
};
/**
* 注册自定义主题
- * @param {string} name - 主题名称
- * @param {Object} config - 主题配置
*/
-export const registerTheme = (name, config) => {
- THEMES[name] = {
- bg: config.bg || THEMES.light.bg,
- text: config.text || THEMES.light.text,
- border: config.border || THEMES.light.border,
- shadow: config.shadow || THEMES.light.shadow,
- hoverShadow: config.hoverShadow || THEMES.light.hoverShadow,
- progressBg: config.progressBg || THEMES.light.progressBg,
- closeHoverBg: config.closeHoverBg || THEMES.light.closeHoverBg,
+export const registerTheme = (name: string, config: ThemeConfig): void => {
+ (THEMES as Record)[name] = {
+ bg: config.bg || (THEMES.light as ThemeConfig).bg,
+ text: config.text || (THEMES.light as ThemeConfig).text,
+ border: config.border || (THEMES.light as ThemeConfig).border,
+ shadow: config.shadow || (THEMES.light as ThemeConfig).shadow,
+ hoverShadow: config.hoverShadow || (THEMES.light as ThemeConfig).hoverShadow,
+ progressBg: config.progressBg || (THEMES.light as ThemeConfig).progressBg,
+ closeHoverBg: config.closeHoverBg || (THEMES.light as ThemeConfig).closeHoverBg,
};
};
/**
* 注销自定义主题
- * @param {string} name - 主题名称
*/
-export const unregisterTheme = (name) => {
+export const unregisterTheme = (name: string): void => {
if (name === 'light' || name === 'dark' || name === 'auto') {
console.warn('Cannot unregister built-in theme:', name);
return;
}
-
- delete THEMES[name];
+ delete (THEMES as Record)[name];
};
/**
* 获取所有主题
- * @returns {Object} 主题映射
*/
-export const getAllThemes = () => {
- return { ...THEMES };
+export const getAllThemes = (): Record => {
+ return { ...THEMES } as Record;
};
/**
* 获取主题名称列表
- * @returns {Array} 主题名称数组
*/
-export const getThemeNames = () => {
+export const getThemeNames = (): string[] => {
return Object.keys(THEMES);
};
/**
* 检查主题是否存在
- * @param {string} name - 主题名称
- * @returns {boolean} 是否存在
*/
-export const hasTheme = (name) => {
+export const hasTheme = (name: string): boolean => {
return name in THEMES;
};
/**
* 获取主题预览
- * @param {string} theme - 主题名称
- * @returns {Object} 主题预览
*/
-export const getThemePreview = (theme) => {
+export const getThemePreview = (theme: string): ThemePreview => {
const config = getThemeConfig(theme);
const resolved = resolveTheme(theme);
-
+
return {
name: theme,
resolved,
@@ -370,13 +321,11 @@ export const getThemePreview = (theme) => {
/**
* 生成主题CSS
- * @param {string} theme - 主题名称
- * @returns {string} CSS字符串
*/
-export const generateThemeCSS = (theme) => {
+export const generateThemeCSS = (theme: string): string => {
const config = getThemeConfig(theme);
const resolved = resolveTheme(theme);
-
+
return `
.met-theme-${resolved} {
--met-bg: ${config.bg};
@@ -392,21 +341,18 @@ export const generateThemeCSS = (theme) => {
/**
* 应用主题CSS
- * @param {string} theme - 主题名称
*/
-export const applyThemeCSS = (theme) => {
+export const applyThemeCSS = (theme: string): void => {
if (typeof document === 'undefined') return;
-
+
const css = generateThemeCSS(theme);
const styleId = 'metona-toast-theme-css';
-
- // 移除旧样式
+
const oldStyle = document.getElementById(styleId);
if (oldStyle) {
oldStyle.remove();
}
-
- // 添加新样式
+
const style = document.createElement('style');
style.id = styleId;
style.textContent = css;
@@ -416,9 +362,9 @@ export const applyThemeCSS = (theme) => {
/**
* 移除主题CSS
*/
-export const removeThemeCSS = () => {
+export const removeThemeCSS = (): void => {
if (typeof document === 'undefined') return;
-
+
const styleId = 'metona-toast-theme-css';
const style = document.getElementById(styleId);
if (style) {
@@ -427,9 +373,9 @@ export const removeThemeCSS = () => {
};
/**
- * 主题工具 — 代理层,直接引用模块函数
+ * 主题工具 — 代理层
*/
-export const themeUtils = {
+export const themeUtils: ThemeUtils = {
getSystemTheme,
resolveTheme,
getThemeConfig,
diff --git a/src/toast.ts b/src/toast.ts
new file mode 100644
index 0000000..775f3cb
--- /dev/null
+++ b/src/toast.ts
@@ -0,0 +1,551 @@
+/**
+ * MetonaToast Toast — Toast 类
+ * @module toast
+ * @version 0.2.0
+ */
+
+import { generateId, escapeHTML } from './utils.js';
+import { DEFAULTS, ICONS, TYPE_COLORS, THEMES } from './constants.js';
+import { injectStyles } from './styles.js';
+import { getTheme } from './themes.js';
+import { t, getLocaleDirection, getCurrentLocale } from './i18n.js';
+import type { ToastConfig, ToastOptions, ToastInstance, ErrorInfo, TypeColor, ThemeConfig } from './types.js';
+
+/**
+ * Toast 类 — 核心通知组件
+ */
+export class Toast implements ToastInstance {
+ // 静态钩子系统
+ static _hooks: Map void>> = new Map();
+
+ // 由 api.ts 注入的回调,避免循环依赖
+ static _onError: ((errorInfo: ErrorInfo) => void) | null = null;
+ static _removeToast: ((id: string) => void) | null = null;
+
+ static on(name: string, fn: (toast: Toast) => void): () => void {
+ if (!this._hooks.has(name)) this._hooks.set(name, []);
+ this._hooks.get(name)!.push(fn);
+ return () => this.off(name, fn);
+ }
+
+ static off(name: string, fn: (toast: Toast) => void): void {
+ const list = this._hooks.get(name);
+ if (list) this._hooks.set(name, list.filter(f => f !== fn));
+ }
+
+ static trigger(name: string, toast: Toast): void {
+ const list = this._hooks.get(name);
+ if (list) {
+ list.forEach(fn => {
+ try { fn(toast); }
+ catch (e) {
+ console.error('Hook error:', name, e);
+ if (Toast._onError) {
+ try { Toast._onError({ hook: name, error: e as Error, toast }); } catch (_e) { /* noop */ }
+ }
+ }
+ });
+ }
+ }
+
+ id: string;
+ type: string;
+ title: string;
+ message: string;
+ html: string;
+ iconHTML: string;
+ config: ToastConfig;
+ el: HTMLElement | null = null;
+ barEl: HTMLElement | null = null;
+ rafId: number | null = null;
+ remaining: number;
+ startedAt: number = 0;
+ paused: boolean = false;
+ closing: boolean = false;
+ group: string | null = null;
+ _cleanups: Array<() => void> = [];
+
+ constructor(opts: ToastOptions) {
+ this.id = opts.id || generateId();
+ this.type = opts.type || 'default';
+ this.title = opts.title || '';
+ this.message = opts.message ?? opts.content ?? '';
+ this.html = opts.html || '';
+ this.iconHTML = opts.iconHTML || '';
+ this.config = { ...DEFAULTS as unknown as ToastConfig, ...opts };
+ // 防止嵌套对象被多个 toast 实例共享引用
+ if (opts.style && typeof opts.style === 'object') {
+ this.config.style = { ...opts.style };
+ }
+ this.remaining = this.config.duration || 0;
+ this.group = opts.group || null;
+ }
+
+ _palette(): { theme: string; c: TypeColor; t: Partial } {
+ const theme = getTheme(this.config.theme || 'auto');
+ const c = (TYPE_COLORS[this.type] as TypeColor) || (TYPE_COLORS.default as TypeColor);
+ const tc = (THEMES[theme] as ThemeConfig) || (THEMES.light as ThemeConfig);
+ const t: Partial = { bg: tc.bg, border: tc.border, shadow: tc.shadow };
+ return { theme, c, t };
+ }
+
+ create(): this {
+ if (typeof window === 'undefined' || typeof document === 'undefined') return this;
+
+ Toast.trigger('beforeShow', this);
+ injectStyles();
+
+ const container = this._getContainer();
+ const { theme, c, t } = this._palette();
+
+ this._limitToasts(container);
+
+ const el = document.createElement('div');
+ el.className = this._buildClassName(theme);
+ el.setAttribute('role', (this.type === 'error' || this.type === 'warning') ? 'alert' : 'status');
+ el.setAttribute('aria-live', this.type === 'error' ? 'assertive' : 'polite');
+ el.dataset.id = this.id;
+
+ this._applyStyles(el, theme, t);
+ this._buildContent(el, c);
+
+ this.el = el;
+ // Chrome bug: column-reverse + appendChild 会导致重叠。改用 column + insertBefore
+ const pos = this.config.position || 'top-right';
+ if (pos.startsWith('top')) {
+ container.insertBefore(el, container.firstChild);
+ } else {
+ container.appendChild(el);
+ }
+
+ this._bindEvents(el);
+ this._startTimer();
+
+ // 进入动画
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ el.classList.add('met-show');
+ });
+ });
+
+ if (typeof this.config.onShow === 'function') {
+ try {
+ this.config.onShow(this);
+ } catch (e) {
+ console.error('onShow callback error:', e);
+ }
+ }
+
+ Toast.trigger('afterShow', this);
+
+ return this;
+ }
+
+ _getContainer(): HTMLElement {
+ const position = this.config.position || 'top-right';
+ const zIndex = this.config.zIndex || 9999;
+
+ // 从模块级缓存获取 (由 api.ts 管理)
+ const cached = _containerCache.get(document.body);
+ if (cached && cached.has(position)) {
+ return cached.get(position)!;
+ }
+
+ // 缓存未命中时创建新容器
+ if (!_containerCache.has(document.body)) {
+ _containerCache.set(document.body, new Map());
+ }
+
+ const el = document.createElement('div');
+ el.className = `met-container ${position}`;
+ el.setAttribute('aria-label', 'Notifications');
+ el.setAttribute('role', 'region');
+
+ const posStyles: Record = {
+ 'top-left': 'top:0;left:0;align-items:flex-start',
+ 'top-center': 'top:0;left:0;right:0;align-items:center',
+ 'top-right': 'top:0;right:0;align-items:flex-end',
+ 'bottom-left': 'bottom:0;left:0;align-items:flex-start',
+ 'bottom-center': 'bottom:0;left:0;right:0;align-items:center',
+ 'bottom-right': 'bottom:0;right:0;align-items:flex-end',
+ };
+ el.style.cssText = `
+ display:flex;
+ flex-direction:column;
+ gap:${this.config.gap || 12}px;
+ box-sizing:border-box;
+ padding:${this.config.offset || 24}px;
+ max-width:100vw;
+ position:fixed;
+ z-index:${zIndex};
+ pointer-events:none;
+ ${posStyles[position] || 'top:0;right:0;align-items:flex-end'}
+ `;
+
+ document.body.appendChild(el);
+ _containerCache.get(document.body)!.set(position, el);
+
+ return el;
+ }
+
+ _limitToasts(container: HTMLElement): void {
+ const max = this.config.max || 6;
+ const list = Array.from(container.querySelectorAll('.met-toast'));
+ if (list.length >= max) {
+ const first = list[0] as HTMLElement;
+ const id = first?.dataset.id;
+ if (id && Toast._removeToast) {
+ Toast._removeToast(id);
+ }
+ }
+ }
+
+ _buildClassName(theme: string): string {
+ const CSS_ANIMS = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom',
+ 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
+ const anim = CSS_ANIMS.includes(this.config.animation || '') ? this.config.animation : 'slide';
+ return [
+ 'met-toast',
+ `met-${this.type}`,
+ `met-theme-${theme}`,
+ `met-anim-${anim}`,
+ (this.config.closeOnClick || this.config.draggable) ? 'met-clickable' : '',
+ this.config.className || '',
+ ].filter(Boolean).join(' ');
+ }
+
+ _applyStyles(el: HTMLElement, _theme: string, _t: Partial): void {
+ const widthValue = typeof this.config.width === 'number'
+ ? `${this.config.width}px`
+ : (typeof this.config.width === 'string' ? this.config.width : '360px');
+
+ const set = (p: string, v: string) => el.style.setProperty(p, v, 'important');
+ set('position', 'relative');
+ set('flex-shrink', '0');
+ set('min-width', '240px');
+ set('max-width', 'calc(100vw - 48px)');
+ set('width', widthValue);
+ // 用户自定义样式最后应用
+ if (this.config.style && Object.keys(this.config.style).length > 0) {
+ Object.entries(this.config.style).forEach(([k, v]) => { (el.style as unknown as Record)[k] = v; });
+ }
+ }
+
+ _buildContent(el: HTMLElement, c: TypeColor): void {
+ // 自定义渲染函数 — 完全接管 DOM 构建
+ if (typeof this.config.render === 'function') {
+ el.innerHTML = this.config.render(this);
+ this.barEl = el.querySelector('.met-bar, .met-bar-v');
+ return;
+ }
+
+ const showIcon = this.config.icon !== false && (this.iconHTML || ICONS[this.type]);
+ const showClose = this.config.closeButton !== false;
+ const showProgress = this.config.showProgress !== false && (this.config.duration || 0) > 0;
+ const showSide = (!showIcon && this.type !== 'default');
+
+ const safeTitle = this.title ? `${escapeHTML(this.title)}
` : '';
+ const safeMessage = this.html
+ ? `${this.html}
`
+ : (this.message ? `${escapeHTML(this.message)}
` : '');
+
+ const iconHTML = showIcon
+ ? `${this.iconHTML || ICONS[this.type]}
` : '';
+ const closeHTML = showClose
+ ? ``
+ : '';
+ const progressHTML = showProgress
+ ? (this.config.progressDirection === 'vertical'
+ ? ``
+ : ``)
+ : '';
+ const sideHTML = showSide
+ ? `` : '';
+
+ el.innerHTML = `
+ ${iconHTML}
+ ${sideHTML}
+
+ ${safeTitle}
+ ${safeMessage}
+
+ ${closeHTML}
+ ${progressHTML}
+ `;
+
+ this.barEl = el.querySelector('.met-bar, .met-bar-v');
+ }
+
+ _bindEvents(el: HTMLElement): void {
+ const eventHandler = (e: Event) => {
+ const target = e.target as HTMLElement;
+
+ if (target.closest('.met-close')) {
+ e.stopPropagation();
+ this.close();
+ return;
+ }
+
+ if (this.config.closeOnClick && !target.closest('.met-close')) {
+ if (typeof this.config.onClick === 'function') {
+ try {
+ this.config.onClick(this);
+ } catch (err) {
+ console.error('onClick callback error:', err);
+ }
+ }
+ this.close();
+ return;
+ }
+ };
+
+ el.addEventListener('click', eventHandler);
+ this._cleanups.push(() => el.removeEventListener('click', eventHandler));
+
+ if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
+ const mouseEnter = () => this._pause();
+ const mouseLeave = () => this._resume();
+
+ el.addEventListener('mouseenter', mouseEnter);
+ el.addEventListener('mouseleave', mouseLeave);
+
+ this._cleanups.push(() => {
+ el.removeEventListener('mouseenter', mouseEnter);
+ el.removeEventListener('mouseleave', mouseLeave);
+ });
+ }
+
+ if (this.config.draggable) {
+ this._bindDrag(el);
+ }
+ }
+
+ _bindDrag(el: HTMLElement): void {
+ let sx = 0, sy = 0, dx = 0, dy = 0, dragging = false;
+
+ const down = (e: PointerEvent) => {
+ if ((e.target as HTMLElement).closest('.met-close')) return;
+ dragging = true;
+ sx = e.clientX;
+ sy = e.clientY;
+ el.setPointerCapture(e.pointerId);
+ el.style.transition = 'none';
+ this._pause();
+ };
+
+ const move = (e: PointerEvent) => {
+ if (!dragging) return;
+ dx = e.clientX - sx;
+ dy = e.clientY - sy;
+ el.style.transform = `translate(${dx}px, ${dy}px) rotate(${dx * 0.1}deg)`;
+ el.style.opacity = String(Math.max(0, 1 - Math.abs(dx) / 200));
+ };
+
+ const up = (e: PointerEvent) => {
+ if (!dragging) return;
+ dragging = false;
+ el.releasePointerCapture(e.pointerId);
+ el.style.transition = '';
+
+ if (Math.abs(dx) > 120) {
+ el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
+ el.style.opacity = '0';
+ setTimeout(() => this.close(true), 250);
+ } else {
+ el.style.transform = '';
+ el.style.opacity = '';
+ this._resume();
+ }
+ dx = dy = 0;
+ };
+
+ el.addEventListener('pointerdown', down);
+ el.addEventListener('pointermove', move);
+ el.addEventListener('pointerup', up);
+ el.addEventListener('pointercancel', up);
+
+ this._cleanups.push(() => {
+ el.removeEventListener('pointerdown', down);
+ el.removeEventListener('pointermove', move);
+ el.removeEventListener('pointerup', up);
+ el.removeEventListener('pointercancel', up);
+ });
+ }
+
+ _startTimer(resuming = false): void {
+ if ((this.config.duration || 0) <= 0) return;
+
+ if (!resuming) {
+ this.startedAt = Date.now();
+ this.remaining = this.config.duration || 0;
+ }
+
+ const tick = (): void => {
+ if (this.paused || this.closing) return;
+
+ try {
+ const elapsed = Date.now() - this.startedAt;
+ this.remaining = Math.max(0, (this.config.duration || 0) - elapsed);
+
+ if (this.barEl) {
+ const ratio = this.remaining / (this.config.duration || 1);
+ const t = this.config.progressDirection === 'vertical'
+ ? `scaleY(${ratio})` : `scaleX(${ratio})`;
+ this.barEl.style.transform = t;
+ }
+
+ if (this.remaining <= 0) {
+ this.close();
+ return;
+ }
+ } catch (e) {
+ console.error('Timer tick error:', e);
+ if (Toast._onError) {
+ try { Toast._onError({ source: 'timer', error: e as Error, toast: this }); } catch (_e) { /* noop */ }
+ }
+ }
+
+ this.rafId = requestAnimationFrame(tick);
+ };
+
+ this.rafId = requestAnimationFrame(tick);
+ }
+
+ _pause(): void {
+ if (this.paused || (this.config.duration || 0) <= 0) return;
+ this.paused = true;
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
+ this.remaining = Math.max(0, (this.config.duration || 0) - (Date.now() - this.startedAt));
+ }
+
+ _resume(): void {
+ if (!this.paused) return;
+ this.paused = false;
+ this.startedAt = Date.now() - ((this.config.duration || 0) - this.remaining);
+ this._startTimer(true);
+ }
+
+ update(partial: Partial): this {
+ Toast.trigger('beforeUpdate', this);
+ const typeChanged = partial.type && partial.type !== this.type;
+ if (partial.type) this.type = partial.type;
+ if (partial.title !== undefined) this.title = partial.title;
+ if (partial.message !== undefined) this.message = partial.message;
+ if (partial.html !== undefined) this.html = partial.html;
+
+ if (!this.el) { Toast.trigger('afterUpdate', this); return this; }
+
+ // 类型变更时同步更新 DOM 类名、边框颜色和进度条颜色
+ if (typeChanged) {
+ const typeClasses = ['met-success', 'met-error', 'met-warning', 'met-info', 'met-loading', 'met-default'];
+ typeClasses.forEach(c => this.el!.classList.remove(c));
+ this.el.classList.add(`met-${this.type}`);
+ if (this.barEl) {
+ const c = (TYPE_COLORS[this.type] as TypeColor) || (TYPE_COLORS.default as TypeColor);
+ this.barEl.style.background = c.fg;
+ }
+ const side = this.el.querySelector('.met-side') as HTMLElement | null;
+ if (side) {
+ const c = (TYPE_COLORS[this.type] as TypeColor) || (TYPE_COLORS.default as TypeColor);
+ side.style.background = c.fg;
+ }
+ }
+
+ const content = this.el.querySelector('.met-content');
+ if (content) {
+ const safeTitle = this.title ? `${escapeHTML(this.title)}
` : '';
+ const safeMessage = this.html
+ ? `${this.html}
`
+ : (this.message ? `${escapeHTML(this.message)}
` : '');
+ content.innerHTML = `${safeTitle}${safeMessage}`;
+ }
+
+ // resetTimerOnUpdate: 更新内容后重置计时器
+ if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
+ this.startedAt = Date.now();
+ this.remaining = this.config.duration || 0;
+ this._startTimer();
+ }
+
+ if (typeof this.config.onUpdate === 'function') {
+ try { this.config.onUpdate(this); } catch (e) { console.error('onUpdate callback error:', e); }
+ }
+
+ Toast.trigger('afterUpdate', this);
+ return this;
+ }
+
+ close(immediate = false): void {
+ if (this.closing) return;
+ this.closing = true;
+
+ Toast.trigger('beforeClose', this);
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
+ this._cleanups.forEach(fn => { try { fn(); } catch (_e) { /* noop */ } });
+ this._cleanups = [];
+
+ const el = this.el;
+ if (!el) {
+ if (Toast._removeToast) Toast._removeToast(this.id);
+ return;
+ }
+
+ // 立即脱离文档流
+ const container = el.parentNode;
+ if (container && !immediate) {
+ const elRect = el.getBoundingClientRect();
+ const containerRect = (container as HTMLElement).getBoundingClientRect();
+ el.style.position = 'absolute';
+ el.style.top = (elRect.top - containerRect.top) + 'px';
+ el.style.left = (elRect.left - containerRect.left) + 'px';
+ el.style.width = elRect.width + 'px';
+ el.style.margin = '0';
+ }
+
+ el.classList.add('met-leaving');
+ el.classList.remove('met-show');
+
+ const pos = this.config.position || 'top-right';
+ const isRTL = getLocaleDirection(getCurrentLocale()) === 'rtl';
+ let transform = 'scale(.96)';
+
+ if (pos.includes('right')) transform = isRTL ? 'translateX(-120%)' : 'translateX(120%)';
+ else if (pos.includes('left')) transform = isRTL ? 'translateX(120%)' : 'translateX(-120%)';
+ else if (pos.startsWith('top')) transform = 'translateY(-20px)';
+ else transform = 'translateY(20px)';
+
+ el.style.transform = transform;
+ el.style.opacity = '0';
+
+ setTimeout(() => this._destroy(), immediate ? 0 : 300);
+ }
+
+ _destroy(): void {
+ if (this.el && this.el.parentNode) {
+ this.el.parentNode.removeChild(this.el);
+ }
+ this.el = null;
+
+ if (typeof this.config.onClose === 'function') {
+ try {
+ this.config.onClose(this);
+ } catch (e) {
+ console.error('onClose callback error:', e);
+ }
+ }
+
+ Toast.trigger('afterClose', this);
+ if (Toast._removeToast) Toast._removeToast(this.id);
+ }
+}
+
+/**
+ * 模块级共享容器缓存,所有 Toast 实例共用
+ */
+export const _containerCache: Map> = new Map();
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..1dd0234
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,552 @@
+/**
+ * MetonaToast — 核心类型定义
+ * @module types
+ * @version 0.2.0
+ */
+
+// ========== 基础类型 ==========
+
+/** Toast 通知类型(107种内置图标类型) */
+export type ToastType =
+ | 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'
+ | 'check' | 'x' | 'alert' | 'question' | 'star' | 'heart' | 'bell' | 'mail'
+ | 'settings' | 'user' | 'home' | 'search' | 'plus' | 'minus' | 'edit'
+ | 'trash' | 'download' | 'upload' | 'share' | 'link' | 'external'
+ | 'clock' | 'calendar' | 'map' | 'compass' | 'globe' | 'wifi' | 'cloud'
+ | 'sun' | 'moon' | 'zap' | 'activity' | 'cpu' | 'database' | 'server'
+ | 'terminal' | 'code' | 'git' | 'package' | 'layers' | 'grid' | 'list'
+ | 'filter' | 'sort' | 'refresh' | 'sync' | 'power' | 'battery' | 'bluetooth'
+ | 'volume' | 'mic' | 'camera' | 'image' | 'video' | 'music' | 'file'
+ | 'folder' | 'clipboard' | 'save' | 'print' | 'eye' | 'eyeOff' | 'lock'
+ | 'unlock' | 'shield' | 'key' | 'flag' | 'bookmark' | 'tag' | 'gift'
+ | 'award' | 'target' | 'crosshair' | 'move' | 'maximize' | 'minimize'
+ | 'copy' | 'cut' | 'paste' | 'rotateCw' | 'rotateCcw' | 'zoomIn' | 'zoomOut'
+ | 'crop' | 'sliders' | 'toggleLeft' | 'toggleRight'
+ | 'checkCircle' | 'xCircle' | 'alertCircle' | 'infoCircle' | 'helpCircle'
+ | 'alertTriangle' | 'checkSquare' | 'square' | 'circle' | 'triangle'
+ | 'hexagon' | 'octagon' | 'pentagon' | 'diamond';
+
+/** Toast 显示位置 */
+export type ToastPosition =
+ | 'top-left' | 'top-center' | 'top-right'
+ | 'bottom-left' | 'bottom-center' | 'bottom-right';
+
+/** Toast 主题 */
+export type ToastTheme = 'light' | 'dark' | 'auto' | 'warm' | (string & {});
+
+/** Toast 动画类型 */
+export type ToastAnimation =
+ | 'slide' | 'fade' | 'scale' | 'bounce' | 'flip' | 'rotate' | 'zoom'
+ | 'slideUp' | 'slideDown' | 'slideLeft' | 'slideRight'
+ | (string & {});
+
+/** 进度条方向 */
+export type ToastProgressDirection = 'horizontal' | 'vertical';
+
+/** 语言代码 */
+export type ToastLocale =
+ | 'zh-CN' | 'zh-TW' | 'en-US' | 'en-GB' | 'ja' | 'ko' | 'fr' | 'de'
+ | 'es' | 'pt' | 'ru' | 'ar' | 'hi' | 'th' | 'vi' | 'id' | 'ms' | 'tr'
+ | 'it' | 'nl' | 'pl' | 'uk' | 'cs' | 'sv' | 'da' | 'fi' | 'nb' | 'el'
+ | 'he' | 'hu' | 'ro' | 'bg' | 'hr' | 'sk' | 'sl' | 'et' | 'lv' | 'lt'
+ | 'ca' | 'gl' | 'eu' | 'cy' | 'ga' | 'mt' | 'is' | 'mk' | 'sq' | 'sr'
+ | 'bs' | 'me' | 'ka' | 'hy' | 'az' | 'uz' | 'kk' | 'ky' | 'tg' | 'tk'
+ | 'mn' | 'ne' | 'si' | 'my' | 'km' | 'lo' | 'am' | 'sw' | 'yo' | 'ig'
+ | 'ha' | 'zu' | 'af' | 'xh' | 'st' | 'tn' | 'ts' | 'ss' | 've' | 'nr';
+
+// ========== 颜色配置 ==========
+
+export interface TypeColor {
+ fg: string;
+ bg: string;
+}
+
+// ========== Toast 配置 ==========
+
+export interface ToastConfig {
+ position?: ToastPosition;
+ duration?: number;
+ max?: number;
+ gap?: number;
+ offset?: number;
+ pauseOnHover?: boolean;
+ closeOnClick?: boolean;
+ draggable?: boolean;
+ showProgress?: boolean;
+ progressDirection?: ToastProgressDirection;
+ icon?: boolean;
+ closeButton?: boolean;
+ theme?: ToastTheme;
+ animation?: ToastAnimation;
+ zIndex?: number;
+ width?: number | string;
+ className?: string;
+ style?: Record;
+ onShow?: ((toast: ToastInstance) => void) | null;
+ onClose?: ((toast: ToastInstance) => void) | null;
+ onClick?: ((toast: ToastInstance) => void) | null;
+ onUpdate?: ((toast: ToastInstance) => void) | null;
+ render?: ((toast: ToastInstance) => string) | null;
+ resetTimerOnUpdate?: boolean;
+ notifyWhenHidden?: boolean;
+ onError?: ((errorInfo: ErrorInfo) => void) | null;
+ group?: string | null;
+ locale?: string;
+ plugins?: Array;
+}
+
+export interface ErrorInfo {
+ hook?: string;
+ source?: string;
+ error: Error;
+ toast?: ToastInstance;
+}
+
+// ========== Toast 选项与实例 ==========
+
+export interface ToastOptions extends Partial {
+ type?: string;
+ title?: string;
+ message?: string;
+ content?: string;
+ html?: string;
+ iconHTML?: string;
+ id?: string;
+}
+
+export interface ToastInstance {
+ id: string;
+ type: string;
+ title: string;
+ message: string;
+ html: string;
+ iconHTML: string;
+ config: ToastConfig;
+ el: HTMLElement | null;
+ barEl: HTMLElement | null;
+ rafId: number | null;
+ remaining: number;
+ startedAt: number;
+ paused: boolean;
+ closing: boolean;
+ group: string | null;
+ _cleanups: Array<() => void>;
+
+ _palette(): { theme: string; c: TypeColor; t: Partial };
+ create(): this;
+ update(partial: Partial): this;
+ close(immediate?: boolean): void;
+ _pause(): void;
+ _resume(): void;
+ _destroy(): void;
+}
+
+// ========== 控制对象 ==========
+
+export interface LoadingControl {
+ id: string;
+ success(message?: string, opts?: ToastOptions): ToastInstance | null;
+ error(message?: string, opts?: ToastOptions): ToastInstance | null;
+ info(message?: string, opts?: ToastOptions): ToastInstance | null;
+ warning(message?: string, opts?: ToastOptions): ToastInstance | null;
+ update(partial: Partial): unknown;
+ dismiss(): void;
+}
+
+export interface ProgressControl {
+ id: string;
+ setProgress(percent: number): void;
+ complete(message?: string): void;
+ error(message?: string): void;
+ dismiss(): void;
+}
+
+export interface CountdownControl {
+ id: string;
+ cancel(): void;
+ pause(): void;
+ resume(): void;
+}
+
+export interface ActionControl {
+ id: string;
+ toast: ToastInstance;
+ dismiss(): void;
+}
+
+export interface QueueControl {
+ then(
+ onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null,
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null
+ ): Promise;
+ catch(
+ onrejected?: ((reason: unknown) => TResult | PromiseLike) | null
+ ): Promise;
+ cancel(): void;
+}
+
+export interface GroupAPI {
+ _group: string;
+ show: (...args: unknown[]) => ToastInstance;
+ success: (...args: unknown[]) => ToastInstance;
+ error: (...args: unknown[]) => ToastInstance;
+ warning: (...args: unknown[]) => ToastInstance;
+ info: (...args: unknown[]) => ToastInstance;
+ loading: (...args: unknown[]) => LoadingControl;
+ action: (...args: unknown[]) => ActionControl;
+ dismiss(): void;
+ count(): number;
+}
+
+// ========== Action 按钮 ==========
+
+export interface ActionButton {
+ text: string;
+ onClick: (toast: ToastInstance) => void;
+ color?: string;
+ style?: Record;
+ close?: boolean;
+}
+
+// ========== Options 接口 ==========
+
+export interface PromiseOptions extends ToastOptions {
+ loading?: string;
+ success?: string;
+ error?: string;
+}
+
+export interface ConfirmOptions extends ToastOptions {
+ confirmText?: string;
+ confirmColor?: string;
+ cancelText?: string;
+ cancelColor?: string;
+}
+
+export interface PromptOptions extends ToastOptions {
+ inputType?: string;
+ placeholder?: string;
+ defaultValue?: string;
+ submitText?: string;
+ submitColor?: string;
+ cancelText?: string;
+ cancelColor?: string;
+}
+
+export interface ProgressOptions extends ToastOptions {
+ progressColor?: string;
+}
+
+export interface CountdownOptions extends ToastOptions {
+ onComplete?: (() => void) | null;
+}
+
+export interface QueueOptions extends ToastOptions {
+ delay?: number;
+}
+
+export interface StackOptions extends ToastOptions {
+ stagger?: number;
+}
+
+export interface InitOptions {
+ config?: Partial;
+ theme?: ToastTheme;
+ locale?: string;
+ plugins?: Array;
+}
+
+export interface StatusInfo {
+ version: string;
+ toasts: number;
+ theme: string;
+ locale: string;
+ plugins: string[];
+ animations: number;
+}
+
+// ========== 动画 ==========
+
+export interface AnimationConfig {
+ enter: Record;
+ leave: Record;
+ duration: number;
+ easing: string;
+ name?: string;
+ delay?: number;
+ iterations?: number;
+ direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
+ fillMode?: 'none' | 'forwards' | 'backwards' | 'both';
+}
+
+export interface AnimationUtils {
+ register(name: string, config: Partial): void;
+ unregister(name: string): void;
+ get(name: string): AnimationConfig | null;
+ getAnimationNames(): string[];
+ getActiveCount(): number;
+ cancelAll(): void;
+ reset(): void;
+ destroy(): void;
+}
+
+// ========== 主题 ==========
+
+export interface ThemeConfig {
+ bg: string;
+ text: string;
+ border: string;
+ shadow: string;
+ hoverShadow: string;
+ progressBg: string;
+ closeHoverBg: string;
+ backdropFilter?: string;
+}
+
+export interface ThemePreview {
+ name: string;
+ resolved: string;
+ colors: {
+ background: string;
+ text: string;
+ border: string;
+ };
+ isDark: boolean;
+ isLight: boolean;
+ isAuto: boolean;
+}
+
+export interface ThemeUtils {
+ getSystemTheme(): 'light' | 'dark';
+ resolveTheme(theme: string): string;
+ getThemeConfig(theme: string): ThemeConfig;
+ applyTheme(theme: string): void;
+ getCurrentTheme(): string;
+ getResolvedTheme(): string;
+ switchTheme(theme: string): void;
+ toggleTheme(): void;
+ resetToAuto(): void;
+ initTheme(): void;
+ watchSystemTheme(): void;
+ unwatchSystemTheme(): void;
+ addThemeListener(listener: (theme: string, resolved: string) => void): () => void;
+ removeThemeListener(listener: (theme: string, resolved: string) => void): void;
+ clearThemeListeners(): void;
+ registerTheme(name: string, config: ThemeConfig): void;
+ unregisterTheme(name: string): void;
+ getAllThemes(): Record;
+ getThemeNames(): string[];
+ hasTheme(name: string): boolean;
+ getThemePreview(theme: string): ThemePreview;
+ generateThemeCSS(theme: string): string;
+ applyThemeCSS(theme: string): void;
+ removeThemeCSS(): void;
+ saveTheme(theme: string): void;
+ loadTheme(): string;
+}
+
+// ========== 国际化 ==========
+
+export interface LocaleInfo {
+ code: string;
+ name: string;
+ direction: 'ltr' | 'rtl';
+ isSupported: boolean;
+ isCurrent: boolean;
+ isFallback: boolean;
+}
+
+export interface I18nUtils {
+ t(key: string, params?: Record): string;
+ plural(key: string, count: number, params?: Record): string;
+ getCurrentLocale(): string;
+ setCurrentLocale(locale: string): void;
+ switchLocale(locale: string): void;
+ getFallbackLocale(): string;
+ setFallbackLocale(locale: string): void;
+ hasTranslation(key: string): boolean;
+ getTranslations(locale: string): Record;
+ addTranslations(locale: string, translations: Record): void;
+ removeTranslation(locale: string, key: string): void;
+ clearTranslations(locale: string): void;
+ getSupportedLocales(): string[];
+ isLocaleSupported(locale: string): boolean;
+ getLocaleName(locale: string): string;
+ getLocaleDirection(locale: string): 'ltr' | 'rtl';
+ getLocaleInfo(locale: string): LocaleInfo;
+ getAllLocaleInfo(): LocaleInfo[];
+ formatNumber(number: number, options?: Intl.NumberFormatOptions): string;
+ formatCurrency(amount: number, currency?: string, options?: Intl.NumberFormatOptions): string;
+ formatPercent(value: number, options?: Intl.NumberFormatOptions): string;
+ formatDate(date: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
+ formatTime(date: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
+ formatRelativeTime(date: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string;
+ formatList(list: string[], options?: Record): string;
+ formatPlural(count: number, options?: Intl.PluralRulesOptions): string;
+ addLocaleListener(listener: (locale: string) => void): () => void;
+ removeLocaleListener(listener: (locale: string) => void): void;
+ clearLocaleListeners(): void;
+ initI18n(): void;
+ saveLocale(locale: string): void;
+ loadLocale(): string;
+ getDefaultLocale(): string;
+}
+
+// ========== 插件 ==========
+
+export interface Plugin {
+ name: string;
+ version?: string;
+ description?: string;
+ hooks?: Record void>;
+ middleware?: (...args: unknown[]) => unknown;
+ install?: (manager: PluginManager) => void | Record | null;
+ uninstall?: (manager: PluginManager) => void;
+ init?: (manager: PluginManager) => void;
+ destroy?: (manager: PluginManager) => void;
+ installed?: boolean;
+ enabled?: boolean;
+ [key: string]: unknown;
+}
+
+export interface PluginManager {
+ register(name: string, plugin: Plugin): PluginManager;
+ unregister(name: string): PluginManager;
+ get(name: string): Plugin | null;
+ has(name: string): boolean;
+ getAll(): Plugin[];
+ getNames(): string[];
+ enable(name: string): PluginManager;
+ disable(name: string): PluginManager;
+ isEnabled(name: string): boolean;
+ destroy(): void;
+}
+
+export interface PluginUtils {
+ createManager(): PluginManager;
+ register(name: string, plugin: Plugin): PluginManager;
+ unregister(name: string): PluginManager;
+ get(name: string): Plugin | null;
+ has(name: string): boolean;
+ getAll(): Plugin[];
+ getNames(): string[];
+ enable(name: string): PluginManager;
+ disable(name: string): PluginManager;
+ isEnabled(name: string): boolean;
+ getPreset(name: string): Plugin | null;
+ getAllPresets(): Record;
+ createPlugin(config: Partial): Plugin;
+ validatePlugin(plugin: Partial): { valid: boolean; errors: string[] };
+}
+
+// ========== MeToast 主接口 ==========
+
+export interface MeToast {
+ version: string;
+ _toasts: Map;
+ _config: ToastConfig;
+ _destroyed?: boolean;
+
+ // 内部方法
+ _emit(opts: ToastOptions): ToastInstance;
+ _remove(id: string): void;
+ _resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null;
+ _groupCount(name: string): number;
+
+ // 配置
+ configure(opts: Partial): MeToast;
+ init(options?: InitOptions): MeToast;
+ destroy(): void;
+ getStatus(): StatusInfo;
+ getConfig(): ToastConfig;
+ updateConfig(config: Partial): MeToast;
+ resetConfig(): MeToast;
+
+ // Toast 方法
+ show(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
+ success(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
+ error(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
+ warning(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
+ info(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
+ loading(messageOrOpts: string | ToastOptions, opts?: ToastOptions): LoadingControl;
+ promise(promise: Promise, opts?: PromiseOptions): Promise;
+ confirm(message: string, opts?: ConfirmOptions): Promise;
+ prompt(message: string, opts?: PromptOptions): Promise;
+ progress(messageOrOpts: string | ToastOptions, opts?: ProgressOptions): ProgressControl;
+ countdown(message: string, seconds?: number, opts?: CountdownOptions): CountdownControl;
+ action(messageOrOpts: string | ToastOptions, actions?: ActionButton[], opts?: ToastOptions): ActionControl;
+ queue(messages: Array, opts?: QueueOptions): QueueControl;
+ stack(messages: Array, opts?: StackOptions): void;
+
+ // 分组
+ group(name: string): GroupAPI;
+ dismissGroup(name: string): void;
+
+ // Toast 管理
+ find(id: string): ToastInstance | undefined;
+ dismiss(id?: string): void;
+ clear(position?: ToastPosition): void;
+ getToasts(): ToastInstance[];
+ count(): number;
+ hasToasts(): boolean;
+ getToast(id: string): ToastInstance | null;
+ closeAll(): void;
+ clearAll(): void;
+ pauseAll(): void;
+ resumeAll(): void;
+ updateAll(partial: Partial): void;
+ findToasts(predicate: (toast: ToastInstance) => boolean): ToastInstance[];
+ findByType(type: ToastType): ToastInstance[];
+ findByPosition(position: ToastPosition): ToastInstance[];
+ getAll(): Map;
+
+ // 插件
+ use(plugin: string | Plugin, options?: Record): MeToast;
+
+ // 子模块
+ animations: AnimationUtils;
+ themes: ThemeUtils;
+ i18n: I18nUtils;
+ plugins: PluginUtils;
+ presetPlugins: Record;
+}
+
+// ========== 钩子名称 ==========
+
+export const HOOK_NAMES = {
+ BEFORE_INIT: 'beforeInit',
+ AFTER_INIT: 'afterInit',
+ BEFORE_DESTROY: 'beforeDestroy',
+ AFTER_DESTROY: 'afterDestroy',
+ BEFORE_SHOW: 'beforeShow',
+ AFTER_SHOW: 'afterShow',
+ BEFORE_CLOSE: 'beforeClose',
+ AFTER_CLOSE: 'afterClose',
+ BEFORE_UPDATE: 'beforeUpdate',
+ AFTER_UPDATE: 'afterUpdate',
+ CONFIG_CHANGE: 'configChange',
+ THEME_CHANGE: 'themeChange',
+ LOCALE_CHANGE: 'localeChange',
+ CLICK: 'click',
+ HOVER: 'hover',
+ DRAG_START: 'dragStart',
+ DRAG_END: 'dragEnd',
+ ANIMATION_START: 'animationStart',
+ ANIMATION_END: 'animationEnd',
+ PROGRESS_START: 'progressStart',
+ PROGRESS_UPDATE: 'progressUpdate',
+ PROGRESS_END: 'progressEnd',
+ ERROR: 'error',
+ CUSTOM: 'custom',
+} as const;
+
+export type HookName = (typeof HOOK_NAMES)[keyof typeof HOOK_NAMES];
+
+// ========== 全局声明 ==========
+
+declare global {
+ interface Window {
+ MeToast: MeToast;
+ Met: MeToast;
+ }
+}
diff --git a/src/utils.js b/src/utils.ts
similarity index 55%
rename from src/utils.js
rename to src/utils.ts
index 965e99d..d417458 100644
--- a/src/utils.js
+++ b/src/utils.ts
@@ -1,24 +1,21 @@
/**
- * MetonaToast Utils - 精简工具函数
+ * MetonaToast Utils — 工具函数
* @module utils
- * @version 0.1.2
- * @description 仅保留核心模块实际使用的工具函数
+ * @version 0.2.0
*/
/**
* 生成唯一ID
- * @returns {string} 唯一ID
*/
-export const generateId = () => {
+export const generateId = (): string => {
return 'met-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
};
/**
* HTML转义
- * @param {string} s - 输入字符串
- * @returns {string} 转义后的字符串
+ * @param s - 输入字符串
*/
-export const escapeHTML = (s) => {
+export const escapeHTML = (s: string | null | undefined): string => {
if (typeof document === 'undefined') {
return String(s == null ? '' : s)
.replace(/&/g, '&')
@@ -35,10 +32,9 @@ export const escapeHTML = (s) => {
/**
* 检测暗色模式偏好
- * @returns {boolean} 是否偏好暗色模式
*/
-export const prefersDark = () => {
+export const prefersDark = (): boolean => {
return typeof window !== 'undefined' &&
- window.matchMedia &&
- window.matchMedia('(prefers-color-scheme: dark)').matches;
+ typeof window.matchMedia === 'function' &&
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
};
diff --git a/tests/index.test.js b/tests/index.test.ts
similarity index 80%
rename from tests/index.test.js
rename to tests/index.test.ts
index bb7ce02..1423236 100644
--- a/tests/index.test.js
+++ b/tests/index.test.ts
@@ -1,53 +1,53 @@
/**
* MetonaToast 单元测试
* @module tests
- * @version 0.1.2
+ * @version 0.2.0
*/
-import MeToast, { Toast, VERSION } from '../src/index.js';
+import MeToast, { Toast, VERSION } from '../src/index';
+import type { ToastInstance, ToastConfig } from '../src/types';
+
+// 模拟DOM元素工厂
+const createMockElement = (): Record => ({
+ className: '',
+ style: {} as Record,
+ setAttribute: jest.fn(),
+ appendChild: jest.fn(),
+ querySelector: jest.fn(() => null),
+ querySelectorAll: jest.fn(() => []),
+ execCommand: jest.fn(() => true),
+ classList: {
+ add: jest.fn(),
+ remove: jest.fn(),
+ contains: jest.fn(),
+ },
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ setPointerCapture: jest.fn(),
+ releasePointerCapture: jest.fn(),
+ animate: jest.fn(() => ({
+ onfinish: null,
+ oncancel: null,
+ cancel: jest.fn(),
+ pause: jest.fn(),
+ play: jest.fn(),
+ })),
+ innerHTML: '',
+ textContent: '',
+ dataset: {} as Record,
+ parentNode: {
+ removeChild: jest.fn(),
+ },
+});
// 模拟DOM环境
const mockDocument = {
- createElement: jest.fn(() => ({
- className: '',
- style: {},
- setAttribute: jest.fn(),
- appendChild: jest.fn(),
- querySelector: jest.fn(),
- querySelectorAll: jest.fn(() => []),
- execCommand: jest.fn(() => true),
- classList: {
- add: jest.fn(),
- remove: jest.fn(),
- contains: jest.fn(),
- },
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- setPointerCapture: jest.fn(),
- releasePointerCapture: jest.fn(),
- animate: jest.fn(() => ({
- onfinish: null,
- oncancel: null,
- cancel: jest.fn(),
- pause: jest.fn(),
- play: jest.fn(),
- })),
- innerHTML: '',
- textContent: '',
- dataset: {},
- parentNode: {
- removeChild: jest.fn(),
- },
- })),
+ createElement: jest.fn(() => createMockElement()),
getElementById: jest.fn(),
querySelector: jest.fn(),
querySelectorAll: jest.fn(() => []),
- head: {
- appendChild: jest.fn(),
- },
- body: {
- appendChild: jest.fn(),
- },
+ head: { appendChild: jest.fn() },
+ body: { appendChild: jest.fn(), querySelectorAll: jest.fn(() => []) },
readyState: 'complete',
addEventListener: jest.fn(),
execCommand: jest.fn(() => true),
@@ -59,73 +59,57 @@ const mockWindow = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
})),
- requestAnimationFrame: jest.fn((cb) => setTimeout(cb, 0)),
+ requestAnimationFrame: jest.fn((cb: FrameRequestCallback) => setTimeout(cb, 0) as unknown as number),
cancelAnimationFrame: jest.fn(),
getComputedStyle: jest.fn(() => ({})),
innerWidth: 1024,
innerHeight: 768,
navigator: {
language: 'zh-CN',
- clipboard: {
- writeText: jest.fn(),
- },
- connection: {
- effectiveType: '4g',
- downlink: 10,
- rtt: 50,
- },
+ userLanguage: undefined,
+ clipboard: { writeText: jest.fn() },
+ connection: { effectiveType: '4g', downlink: 10, rtt: 50 },
onLine: true,
},
- location: {
- search: '',
- href: 'http://localhost',
- },
- history: {
- pushState: jest.fn(),
- },
+ location: { search: '', href: 'http://localhost' },
+ history: { pushState: jest.fn() },
localStorage: {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
},
- Audio: jest.fn(() => ({
- play: jest.fn(),
- volume: 0,
- })),
+ Audio: jest.fn(() => ({ play: jest.fn(), volume: 0 })),
+ Notification: undefined,
};
// 设置全局变量
-global.document = mockDocument;
-global.window = mockWindow;
-global.navigator = mockWindow.navigator;
-global.localStorage = mockWindow.localStorage;
-global.requestAnimationFrame = mockWindow.requestAnimationFrame;
-global.cancelAnimationFrame = mockWindow.cancelAnimationFrame;
-global.matchMedia = mockWindow.matchMedia;
-global.getComputedStyle = mockWindow.getComputedStyle;
-global.Audio = mockWindow.Audio;
+(global as Record).document = mockDocument;
+(global as Record).window = mockWindow;
+(global as Record).navigator = mockWindow.navigator;
+(global as Record).localStorage = mockWindow.localStorage;
+(global as Record).requestAnimationFrame = mockWindow.requestAnimationFrame;
+(global as Record).cancelAnimationFrame = mockWindow.cancelAnimationFrame;
// 测试套件
-describe('MetonaToast', () => {
+ describe('MetonaToast', () => {
beforeEach(() => {
jest.clearAllMocks();
MeToast._toasts.clear();
- // 清理DOM残留
- if (typeof document !== 'undefined') {
- document.querySelectorAll('.met-container').forEach(c => c.remove());
- }
// 清理钩子残留
Toast._hooks.clear();
+ // 重置 _removeToast 回调
+ const { _containerCache } = require('../src/toast.js');
+ _containerCache.clear();
});
-
+
describe('版本信息', () => {
test('应该有正确的版本号', () => {
- expect(VERSION).toBe('0.1.2');
- expect(MeToast.version).toBe('0.1.2');
+ expect(VERSION).toBe('0.2.0');
+ expect(MeToast.version).toBe('0.2.0');
});
});
-
+
describe('基础功能', () => {
test('应该能够显示成功Toast', () => {
const toast = MeToast.success('成功消息');
@@ -134,44 +118,44 @@ describe('MetonaToast', () => {
expect(toast.type).toBe('success');
expect(toast.message).toBe('成功消息');
});
-
+
test('应该能够显示错误Toast', () => {
const toast = MeToast.error('错误消息');
expect(toast).toBeDefined();
expect(toast.type).toBe('error');
expect(toast.message).toBe('错误消息');
});
-
+
test('应该能够显示警告Toast', () => {
const toast = MeToast.warning('警告消息');
expect(toast).toBeDefined();
expect(toast.type).toBe('warning');
expect(toast.message).toBe('警告消息');
});
-
+
test('应该能够显示信息Toast', () => {
const toast = MeToast.info('信息消息');
expect(toast).toBeDefined();
expect(toast.type).toBe('info');
expect(toast.message).toBe('信息消息');
});
-
+
test('应该能够显示默认Toast', () => {
const toast = MeToast.show('默认消息');
expect(toast).toBeDefined();
expect(toast.type).toBe('default');
expect(toast.message).toBe('默认消息');
});
-
+
test('应该能够显示带标题的Toast', () => {
const toast = MeToast.success({
title: '成功',
message: '操作成功',
- });
+ } as Record);
expect(toast.title).toBe('成功');
expect(toast.message).toBe('操作成功');
});
-
+
test('应该能够显示加载Toast', () => {
const loading = MeToast.loading('加载中...');
expect(loading).toBeDefined();
@@ -184,76 +168,64 @@ describe('MetonaToast', () => {
expect(loading.dismiss).toBeInstanceOf(Function);
});
});
-
+
describe('Toast管理', () => {
test('应该能够获取所有Toast', () => {
MeToast.success('消息1');
MeToast.error('消息2');
-
const toasts = MeToast.getToasts();
expect(toasts).toHaveLength(2);
});
-
+
test('应该能够获取Toast数量', () => {
MeToast.success('消息1');
MeToast.error('消息2');
MeToast.warning('消息3');
-
expect(MeToast.count()).toBe(3);
});
-
+
test('应该能够检查是否有Toast', () => {
expect(MeToast.hasToasts()).toBe(false);
-
MeToast.success('消息');
-
expect(MeToast.hasToasts()).toBe(true);
});
-
+
test('应该能够查找Toast', () => {
const toast = MeToast.success('消息');
const found = MeToast.find(toast.id);
-
expect(found).toBeDefined();
- expect(found.id).toBe(toast.id);
+ expect(found!.id).toBe(toast.id);
});
-
+
test('应该能够关闭Toast', () => {
const toast = MeToast.success('消息');
toast.close();
-
// 等待关闭动画
setTimeout(() => {
expect(MeToast.count()).toBe(0);
}, 300);
});
-
+
test('应该能够关闭所有Toast', () => {
MeToast.success('消息1');
MeToast.error('消息2');
MeToast.warning('消息3');
-
MeToast.dismiss();
-
- // 等待关闭动画
setTimeout(() => {
expect(MeToast.count()).toBe(0);
}, 300);
});
-
+
test('应该能够清除所有Toast', () => {
MeToast.success('消息1');
MeToast.error('消息2');
-
MeToast.clear();
-
- // 等待关闭动画
setTimeout(() => {
expect(MeToast.count()).toBe(0);
}, 300);
});
});
-
+
describe('配置管理', () => {
test('应该能够配置全局选项', () => {
MeToast.configure({
@@ -261,50 +233,43 @@ describe('MetonaToast', () => {
duration: 5000,
theme: 'dark',
});
-
const config = MeToast.getConfig();
expect(config.position).toBe('bottom-center');
expect(config.duration).toBe(5000);
expect(config.theme).toBe('dark');
});
-
+
test('应该能够重置配置', () => {
MeToast.configure({
position: 'bottom-center',
duration: 5000,
});
-
MeToast.resetConfig();
-
const config = MeToast.getConfig();
expect(config.position).toBe('top-right');
expect(config.duration).toBe(4000);
});
-
+
test('应该能够更新配置', () => {
- MeToast.updateConfig({
- position: 'top-left',
- });
-
+ MeToast.updateConfig({ position: 'top-left' });
const config = MeToast.getConfig();
expect(config.position).toBe('top-left');
});
});
-
+
describe('主题系统', () => {
test('应该能够获取当前主题', () => {
const theme = MeToast.themes.getCurrentTheme();
expect(theme).toBeDefined();
});
-
+
test('应该能够切换主题', () => {
MeToast.themes.switchTheme('dark');
expect(MeToast.themes.getCurrentTheme()).toBe('dark');
-
MeToast.themes.switchTheme('light');
expect(MeToast.themes.getCurrentTheme()).toBe('light');
});
-
+
test('应该能够获取主题配置', () => {
const config = MeToast.themes.getThemeConfig('light');
expect(config).toBeDefined();
@@ -312,14 +277,14 @@ describe('MetonaToast', () => {
expect(config.text).toBeDefined();
expect(config.border).toBeDefined();
});
-
+
test('应该能够获取所有主题', () => {
const themes = MeToast.themes.getAllThemes();
expect(themes).toBeDefined();
expect(themes.light).toBeDefined();
expect(themes.dark).toBeDefined();
});
-
+
test('应该能够注册自定义主题', () => {
MeToast.themes.registerTheme('custom', {
bg: 'rgba(255, 255, 255, 0.96)',
@@ -330,140 +295,115 @@ describe('MetonaToast', () => {
progressBg: 'rgba(0, 0, 0, 0.06)',
closeHoverBg: 'rgba(0, 0, 0, 0.06)',
});
-
expect(MeToast.themes.hasTheme('custom')).toBe(true);
});
});
-
+
describe('国际化系统', () => {
test('应该能够获取当前语言', () => {
const locale = MeToast.i18n.getCurrentLocale();
expect(locale).toBeDefined();
});
-
+
test('应该能够切换语言', () => {
MeToast.i18n.switchLocale('en-US');
expect(MeToast.i18n.getCurrentLocale()).toBe('en-US');
-
MeToast.i18n.switchLocale('zh-CN');
expect(MeToast.i18n.getCurrentLocale()).toBe('zh-CN');
});
-
+
test('应该能够获取翻译', () => {
const text = MeToast.i18n.t('success');
expect(text).toBeDefined();
});
-
+
test('应该能够检查翻译是否存在', () => {
expect(MeToast.i18n.hasTranslation('success')).toBe(true);
expect(MeToast.i18n.hasTranslation('nonexistent')).toBe(false);
});
-
+
test('应该能够获取支持的语言列表', () => {
const locales = MeToast.i18n.getSupportedLocales();
expect(locales).toContain('zh-CN');
expect(locales).toContain('en-US');
});
-
+
test('应该能够获取语言名称', () => {
const name = MeToast.i18n.getLocaleName('zh-CN');
expect(name).toBe('简体中文');
});
-
+
test('应该能够格式化数字', () => {
const formatted = MeToast.i18n.formatNumber(1234567.89);
expect(formatted).toBeDefined();
});
-
+
test('应该能够格式化货币', () => {
const formatted = MeToast.i18n.formatCurrency(99.99, 'USD');
expect(formatted).toBeDefined();
});
-
+
test('应该能够格式化日期', () => {
const formatted = MeToast.i18n.formatDate(new Date());
expect(formatted).toBeDefined();
});
});
-
+
describe('插件系统', () => {
test('应该能够注册插件', () => {
- const plugin = {
- name: 'test-plugin',
- version: '1.0.0',
- install: jest.fn(),
- };
-
+ const plugin = { name: 'test-plugin', version: '1.0.0', install: jest.fn() };
MeToast.plugins.register('test', plugin);
expect(MeToast.plugins.has('test')).toBe(true);
});
-
+
test('应该能够获取插件', () => {
- const plugin = {
- name: 'test-plugin',
- version: '1.0.0',
- };
-
+ const plugin = { name: 'test-plugin', version: '1.0.0' };
MeToast.plugins.register('test', plugin);
const retrieved = MeToast.plugins.get('test');
expect(retrieved).toBeDefined();
- expect(retrieved.name).toBe('test-plugin');
+ expect(retrieved!.name).toBe('test-plugin');
});
-
+
test('应该能够注销插件', () => {
- const plugin = {
- name: 'test-plugin',
- version: '1.0.0',
- uninstall: jest.fn(),
- };
-
+ const plugin = { name: 'test-plugin', version: '1.0.0', uninstall: jest.fn() };
MeToast.plugins.register('test', plugin);
MeToast.plugins.unregister('test');
expect(MeToast.plugins.has('test')).toBe(false);
});
-
+
test('应该能够启用/禁用插件', () => {
- const plugin = {
- name: 'test-plugin',
- version: '1.0.0',
- };
-
+ const plugin = { name: 'test-plugin', version: '1.0.0' };
MeToast.plugins.register('test', plugin);
-
MeToast.plugins.disable('test');
expect(MeToast.plugins.isEnabled('test')).toBe(false);
-
MeToast.plugins.enable('test');
expect(MeToast.plugins.isEnabled('test')).toBe(true);
});
-
+
test('应该能够获取所有插件', () => {
- // 清理前面测试遗留的插件
['test', 'test-plugin', 'custom'].forEach(n => {
if (MeToast.plugins.has(n)) MeToast.plugins.unregister(n);
});
MeToast.plugins.register('test1', { name: 'test1' });
MeToast.plugins.register('test2', { name: 'test2' });
-
const plugins = MeToast.plugins.getAll();
expect(plugins).toHaveLength(2);
});
-
+
test('应该能够获取插件名称', () => {
MeToast.plugins.register('test1', { name: 'test1' });
MeToast.plugins.register('test2', { name: 'test2' });
-
const names = MeToast.plugins.getNames();
expect(names).toContain('test1');
expect(names).toContain('test2');
});
-
+
test('应该能够使用预设插件', () => {
MeToast.use('keyboard');
expect(MeToast.plugins.has('keyboard')).toBe(true);
});
});
-
+
describe('动画系统', () => {
test('应该能够获取所有动画名称', () => {
const names = MeToast.animations.getAnimationNames();
@@ -472,60 +412,45 @@ describe('MetonaToast', () => {
expect(names).toContain('scale');
expect(names).toContain('bounce');
});
-
+
test('应该能够获取动画配置', () => {
const config = MeToast.animations.get('slide');
expect(config).toBeDefined();
- expect(config.enter).toBeDefined();
- expect(config.leave).toBeDefined();
- expect(config.duration).toBeDefined();
- expect(config.easing).toBeDefined();
+ expect(config!.enter).toBeDefined();
+ expect(config!.leave).toBeDefined();
+ expect(config!.duration).toBeDefined();
+ expect(config!.easing).toBeDefined();
});
-
+
test('应该能够注册自定义动画', () => {
MeToast.animations.register('custom', {
- enter: {
- transform: 'scale(0)',
- opacity: 0,
- },
- leave: {
- transform: 'scale(1)',
- opacity: 1,
- },
+ enter: { transform: 'scale(0)', opacity: '0' },
+ leave: { transform: 'scale(1)', opacity: '1' },
duration: 500,
});
-
expect(MeToast.animations.get('custom')).toBeDefined();
});
-
+
test('应该能够注销动画', () => {
- MeToast.animations.register('custom', {
- enter: {},
- leave: {},
- });
-
+ MeToast.animations.register('custom', { enter: {}, leave: {} });
MeToast.animations.unregister('custom');
expect(MeToast.animations.get('custom')).toBeNull();
});
});
-
+
describe('Promise支持', () => {
test('应该能够使用Promise风格', async () => {
const promise = Promise.resolve('success');
-
await MeToast.promise(promise, {
loading: '加载中...',
success: '成功',
error: '失败',
});
-
- // 验证Toast被创建
expect(MeToast.count()).toBeGreaterThan(0);
});
-
+
test('应该能够处理Promise拒绝', async () => {
const promise = Promise.reject(new Error('error'));
-
try {
await MeToast.promise(promise, {
loading: '加载中...',
@@ -537,44 +462,27 @@ describe('MetonaToast', () => {
}
});
});
-
+
describe('Toast实例', () => {
test('应该能够更新Toast', () => {
const toast = MeToast.success('原始消息');
-
- toast.update({
- title: '新标题',
- message: '新消息',
- type: 'info',
- });
-
+ toast.update({ title: '新标题', message: '新消息', type: 'info' });
expect(toast.title).toBe('新标题');
expect(toast.message).toBe('新消息');
expect(toast.type).toBe('info');
});
-
- test('应该能够暂停和恢复Toast', () => {
- const toast = MeToast.success({
- message: '消息',
- duration: 5000,
- });
+ test('应该能够暂停和恢复Toast', () => {
+ const toast = MeToast.success({ message: '消息', duration: 5000 } as Record);
toast._pause();
expect(toast.paused).toBe(true);
-
toast._resume();
expect(toast.paused).toBe(false);
});
test('暂停恢复应保持 remaining 时间连续性', () => {
- const toast = MeToast.success({
- message: '消息',
- duration: 5000,
- });
-
- // 模拟计时开始
+ const toast = MeToast.success({ message: '消息', duration: 5000 } as Record);
toast.config.duration = 5000;
- // 直接设置内部状态模拟已运行 2 秒
toast.startedAt = Date.now() - 2000;
toast.remaining = 3000;
toast.paused = false;
@@ -587,7 +495,6 @@ describe('MetonaToast', () => {
const pausedRemaining = toast.remaining;
toast._resume();
expect(toast.paused).toBe(false);
- // resume 后 remaining 应保持与暂停时一致(允许微小误差)
expect(toast.remaining).toBeGreaterThanOrEqual(pausedRemaining - 100);
});
@@ -596,7 +503,6 @@ describe('MetonaToast', () => {
expect(toast.type).toBe('info');
toast.update({ type: 'error', message: 'changed' });
expect(toast.type).toBe('error');
- // DOM 验证需要实际浏览器环境,此处验证状态变更
});
test('update 类型变更无 DOM 时不抛异常', () => {
@@ -604,25 +510,23 @@ describe('MetonaToast', () => {
expect(() => toast.update({ type: 'success', message: 'ok' })).not.toThrow();
expect(toast.type).toBe('success');
});
-
+
test('应该能够获取Toast配置', () => {
const toast = MeToast.success({
message: '消息',
position: 'bottom-center',
duration: 5000,
- });
-
+ } as Record);
expect(toast.config.position).toBe('bottom-center');
expect(toast.config.duration).toBe(5000);
});
});
-
+
describe('状态信息', () => {
test('应该能够获取状态信息', () => {
MeToast.success('消息');
-
const status = MeToast.getStatus();
- expect(status.version).toBe('0.1.2');
+ expect(status.version).toBe('0.2.0');
expect(status.toasts).toBeGreaterThanOrEqual(0);
expect(status.theme).toBeDefined();
expect(status.locale).toBeDefined();
@@ -630,15 +534,12 @@ describe('MetonaToast', () => {
expect(status.animations).toBeGreaterThanOrEqual(0);
});
});
-
+
describe('销毁功能', () => {
test('应该能够销毁所有Toast', () => {
MeToast.success('消息1');
MeToast.error('消息2');
-
MeToast.destroy();
-
- // 等待销毁完成
setTimeout(() => {
expect(MeToast.count()).toBe(0);
}, 300);
@@ -648,141 +549,95 @@ describe('MetonaToast', () => {
describe('Toast类', () => {
test('应该能够创建Toast实例', () => {
- const toast = new Toast({
- type: 'success',
- title: '成功',
- message: '操作成功',
- });
-
+ const toast = new Toast({ type: 'success', title: '成功', message: '操作成功' });
expect(toast).toBeDefined();
expect(toast.type).toBe('success');
expect(toast.title).toBe('成功');
expect(toast.message).toBe('操作成功');
});
-
+
test('应该能够生成唯一ID', () => {
const toast1 = new Toast({ message: '消息1' });
const toast2 = new Toast({ message: '消息2' });
-
expect(toast1.id).not.toBe(toast2.id);
});
-
+
test('应该能够设置默认配置', () => {
const toast = new Toast({ message: '消息' });
-
expect(toast.config).toBeDefined();
expect(toast.config.position).toBe('top-right');
expect(toast.config.duration).toBe(4000);
expect(toast.config.max).toBe(6);
});
-
+
test('应该能够合并配置', () => {
- const toast = new Toast({
- message: '消息',
- position: 'bottom-center',
- duration: 5000,
- });
-
+ const toast = new Toast({ message: '消息', position: 'bottom-center', duration: 5000 });
expect(toast.config.position).toBe('bottom-center');
expect(toast.config.duration).toBe(5000);
});
-
+
test('应该能够设置类型', () => {
const types = ['success', 'error', 'warning', 'info', 'loading', 'default'];
-
types.forEach((type) => {
const toast = new Toast({ type, message: '消息' });
expect(toast.type).toBe(type);
});
});
-
+
test('应该能够设置标题', () => {
- const toast = new Toast({
- title: '标题',
- message: '消息',
- });
-
+ const toast = new Toast({ title: '标题', message: '消息' });
expect(toast.title).toBe('标题');
});
-
+
test('应该能够设置消息', () => {
- const toast = new Toast({
- message: '消息内容',
- });
-
+ const toast = new Toast({ message: '消息内容' });
expect(toast.message).toBe('消息内容');
});
-
+
test('应该能够设置HTML内容', () => {
- const toast = new Toast({
- html: '加粗',
- });
-
+ const toast = new Toast({ html: '加粗' });
expect(toast.html).toBe('加粗');
});
-
+
test('应该能够设置自定义图标', () => {
- const toast = new Toast({
- iconHTML: '',
- });
-
+ const toast = new Toast({ iconHTML: '' });
expect(toast.iconHTML).toBe('');
});
-
+
test('应该能够更新Toast', () => {
const toast = new Toast({ message: '原始消息' });
-
- toast.update({
- title: '新标题',
- message: '新消息',
- type: 'success',
- });
-
+ toast.update({ title: '新标题', message: '新消息', type: 'success' });
expect(toast.title).toBe('新标题');
expect(toast.message).toBe('新消息');
expect(toast.type).toBe('success');
});
-
+
test('应该能够关闭Toast', (done) => {
const toast = new Toast({ message: '消息' });
-
toast.close();
-
expect(toast.closing).toBe(true);
- // _destroy 在 300ms setTimeout 中异步调用
setTimeout(() => {
expect(toast.el).toBeNull();
done();
}, 350);
});
-
+
test('应该能够暂停计时', () => {
- const toast = new Toast({
- message: '消息',
- duration: 5000,
- });
-
+ const toast = new Toast({ message: '消息', duration: 5000 });
toast._pause();
-
expect(toast.paused).toBe(true);
});
-
+
test('应该能够恢复计时', () => {
- const toast = new Toast({
- message: '消息',
- duration: 5000,
- });
-
+ const toast = new Toast({ message: '消息', duration: 5000 });
toast._pause();
toast._resume();
-
expect(toast.paused).toBe(false);
});
-
+
test('应该能够获取配色方案', () => {
const toast = new Toast({ type: 'success' });
const palette = toast._palette();
-
expect(palette).toBeDefined();
expect(palette.theme).toBeDefined();
expect(palette.c).toBeDefined();
@@ -793,25 +648,21 @@ describe('Toast类', () => {
describe('工具函数', () => {
test('应该能够生成唯一ID', () => {
const { generateId } = require('../src/utils.js');
-
const id1 = generateId();
const id2 = generateId();
-
expect(id1).not.toBe(id2);
expect(id1).toMatch(/^met-/);
});
-
+
test('应该能够转义HTML', () => {
const { escapeHTML } = require('../src/utils.js');
-
const escaped = escapeHTML('');
expect(escaped).not.toContain('