├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── stale.yml └── workflows │ ├── release.yml │ └── deploy.yml ├── src ├── constants.ts ├── utils.ts ├── theme.ts ├── index.ts ├── shortcuts.ts └── rules.ts ├── eslint.config.js ├── docs ├── .vitepress │ ├── theme │ │ ├── index.ts │ │ ├── Layout.vue │ │ └── style.scss │ ├── plugins │ │ └── externalLinkIcon.ts │ └── config.ts ├── uno.config.ts └── src │ ├── guide │ ├── comparisons.md │ ├── index.md │ └── migration.md │ ├── index.md │ ├── interactive-documentation.md │ ├── animations │ ├── index.md │ ├── spin.md │ ├── animation-properties.md │ ├── fade.md │ ├── zoom.md │ └── slide.md │ └── public │ └── logo.svg ├── vitest.config.ts ├── .ncurc.cjs ├── tsconfig.node.json ├── .gitignore ├── eslintConfig.ts ├── changelogithub.config.ts ├── tsconfig.json ├── test ├── data │ └── index.ts ├── utils │ └── index.ts ├── layer.test.ts ├── base.test.ts ├── fade.test.ts ├── zoom.test.ts ├── spin.test.ts └── slide.test.ts ├── LICENSE ├── README.md └── package.json /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const CSS_VARIABLE_PREFIX = '--una' 2 | export const ENTER_ANIMATION_NAME = 'una-in' 3 | export const EXIT_ANIMATION_NAME = 'una-out' 4 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { bundleRequire } from 'bundle-require' 2 | 3 | 4 | export default await bundleRequire({ filepath: './eslintConfig.ts' }) 5 | .then(({ mod }) => mod.default) 6 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import 'virtual:uno.css' 2 | 3 | import type { Theme } from 'vitepress' 4 | import DefaultTheme from 'vitepress/theme' 5 | import { h } from 'vue' 6 | import Layout from './Layout.vue' 7 | 8 | import './style.scss' 9 | 10 | 11 | export default { 12 | 'extends': DefaultTheme, 13 | Layout: () => h(Layout, null, {}), 14 | } satisfies Theme 15 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import tsconfigPaths from 'vite-tsconfig-paths' 2 | import { defineConfig } from 'vitest/config' 3 | 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | tsconfigPaths(), 8 | ], 9 | test: { 10 | coverage: { 11 | provider: 'v8', 12 | reporter: ['text-summary', 'text'], 13 | include: ['src'], 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 7 3 | exemptLabels: 4 | - pinned 5 | - security 6 | - no-stale 7 | - no stale 8 | - pr welcome 9 | staleLabel: stale 10 | markComment: > 11 | This issue has been automatically marked as stale because it has not had 12 | recent activity. It will be closed if no further activity occurs. Thank you 13 | for your contributions. 14 | closeComment: false 15 | -------------------------------------------------------------------------------- /.ncurc.cjs: -------------------------------------------------------------------------------- 1 | const MINOR_PACKAGES = new Set([ 2 | '@types/node', 3 | 'eslint', 4 | ]) 5 | 6 | 7 | const PATCH_PACKAGES = new Set([]) 8 | 9 | 10 | module.exports = { 11 | format: 'group', 12 | interactive: true, 13 | target: (packageName) => { 14 | if (MINOR_PACKAGES.has(packageName)) 15 | return 'minor' 16 | 17 | if (PATCH_PACKAGES.has(packageName)) 18 | return 'patch' 19 | 20 | return 'latest' 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "noUncheckedIndexedAccess": true, 10 | }, 11 | "include": [ 12 | "build.config.ts", 13 | "changelogithub.config.ts", 14 | "eslintConfig.ts", 15 | "vitest.config.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /*.log* 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pnpm-debug.log* 7 | lerna-debug.log* 8 | 9 | node_modules 10 | dist 11 | dist-ssr 12 | *.local 13 | 14 | # Editor directories and files 15 | .vscode/* 16 | !.vscode/extensions.json 17 | .idea 18 | .DS_Store 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | # VitePress 26 | docs/.vitepress/dist 27 | docs/.vitepress/cache 28 | 29 | # tsc 30 | /*.tsbuildinfo 31 | -------------------------------------------------------------------------------- /docs/uno.config.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@unocss/preset-mini' 2 | import { 3 | defineConfig, 4 | presetIcons, 5 | presetWind3, 6 | transformerDirectives, 7 | transformerVariantGroup, 8 | } from 'unocss' 9 | 10 | 11 | export default defineConfig({ 12 | blocklist: ['outline'], 13 | presets: [ 14 | presetWind3(), 15 | presetIcons(), 16 | ], 17 | transformers: [ 18 | transformerVariantGroup(), 19 | transformerDirectives(), 20 | ], 21 | theme: { 22 | colors: { 23 | primary: { 24 | DEFAULT: 'var(--vp-c-brand-1)', 25 | }, 26 | }, 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /eslintConfig.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@aelita-dev/eslint-config' 2 | 3 | 4 | export default defineConfig( 5 | { 6 | stylistic: { 7 | semi: false, 8 | commaDangle: 'always-multiline', 9 | indent: 2, 10 | quotes: 'single', 11 | }, 12 | 'import': { 13 | ruleOptions: { 14 | order: { 15 | typeImportsFirst: true, 16 | }, 17 | }, 18 | }, 19 | typescript: { 20 | projectType: 'lib', 21 | }, 22 | vue: { 23 | ruleOptions: { 24 | multiWordComponentNames: { ignores: ['Layout'] }, 25 | }, 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /changelogithub.config.ts: -------------------------------------------------------------------------------- 1 | import type { ChangelogOptions } from 'changelogithub' 2 | 3 | 4 | export default { 5 | types: { 6 | feat: { title: '🚀 Features' }, 7 | fix: { title: '🐞 Bug Fixes' }, 8 | perf: { title: '🔥 Performance' }, 9 | refactor: { title: '💅 Refactors' }, 10 | docs: { title: '📖 Documentation' }, 11 | build: { title: '🛠️ Build' }, 12 | types: { title: '🌊 Types' }, 13 | examples: { title: '🏀 Examples' }, 14 | test: { title: '🧪 Tests' }, 15 | style: { title: '🎨 Styles' }, 16 | ci: { title: '🤖 CI' }, 17 | deps: { title: '📦 Dependencies' }, 18 | }, 19 | } satisfies ChangelogOptions 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noUncheckedIndexedAccess": true, 11 | "resolveJsonModule": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "~/*": ["test/*"] 16 | } 17 | }, 18 | "include": [ 19 | "src", 20 | "test", 21 | "docs", 22 | "**/.vitepress/**/*.ts", 23 | "**/.vitepress/**/*.vue" 24 | ], 25 | "references": [ 26 | { "path": "./tsconfig.node.json" } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/data/index.ts: -------------------------------------------------------------------------------- 1 | export const INTEGERS_0_TO_100 = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] 2 | export const INTEGERS = [-200, -190, -180, -170, -160, -150, -140, -130, -120, -110, -100, -90, -80, -70, -60, -50, -40, -30, -20, -10, ...INTEGERS_0_TO_100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200] 3 | 4 | export const DECIMALS_0_TO_100 = [0.1, 10.1, 52.1, 66.66, 99.9] 5 | export const DECIMALS = [-199.9, -180.37, -66.66, -52.1, -10.1, -0.1, ...DECIMALS_0_TO_100, 180.37, 199.9] 6 | 7 | export const FRACTIONS = ['-1/6', '-5/6', '-1/4', '-3/4', '-1/3', '-2/3', '1/6', '5/6', '1/4', '3/4', '1/3', '2/3'] 8 | 9 | export const CSS_VARIABLES = ['$foo', '$foo-bar', '$fooBar'] 10 | -------------------------------------------------------------------------------- /docs/src/guide/comparisons.md: -------------------------------------------------------------------------------- 1 | # Comparisons with other libraries 2 | 3 | ## tailwindcss-animate 4 | 5 | The original `tailwindcss-animate` plugin consists with 2 parts 6 | - Animations 7 | - Animation property modifiers 8 | 9 | 10 | Animation property modifiers are already supported by `@unocss/preset-wind3`. Since this preset is based on it, so that part is removed. 11 | 12 | Options for configuring default value of animation properties are provided. Please refer to [Preset's Options](/guide/#options). 13 | 14 | Animations are way more flexible and powerful. Please refer to [Animations](/animations/). 15 | 16 |
17 | 18 | For detailed migration guide, please refer to [Migrate from `tailwindcss-animate`](./migration#tailwindcss-animate). 19 | -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { PresetAnimationsOptions } from '@/index' 2 | import type { UnoGenerator } from '@unocss/core' 3 | import type { Theme } from '@unocss/preset-mini' 4 | import { createGenerator } from '@unocss/core' 5 | import { presetWind4 } from 'unocss' 6 | import { presetAnimations } from '@/index' 7 | 8 | 9 | interface GeneratorOptions { 10 | presetOptions?: PresetAnimationsOptions 11 | theme?: Theme 12 | } 13 | 14 | 15 | export async function generator(options: GeneratorOptions = {}): Promise> { 16 | return await createGenerator({ 17 | presets: [ 18 | // @ts-expect-error - vue-tsc issue 19 | presetWind4({ preflights: { reset: false } }), 20 | presetAnimations(options.presetOptions), 21 | ], 22 | theme: options.theme, 23 | }) 24 | } 25 | 26 | 27 | export const uno = await generator() 28 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 33 | 34 | 35 | 41 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { h } from '@unocss/preset-mini/utils' 2 | import { DEFAULT_SLIDE_TRANSLATE } from '@/rules' 3 | 4 | 5 | function normalizeDirection(dir: string | undefined): string | undefined { 6 | const dirMap: Record = { 7 | t: 'top', 8 | b: 'bottom', 9 | l: 'left', 10 | r: 'right', 11 | } 12 | 13 | return dirMap[dir ?? ''] ?? dir 14 | } 15 | 16 | 17 | export function handleSlide( 18 | val: string | undefined, 19 | dir: string | undefined, 20 | ): [value?: string | undefined, direction?: string | undefined] { 21 | let value = h.cssvar.fraction.rem(val || DEFAULT_SLIDE_TRANSLATE) 22 | 23 | if (!value) 24 | return [] 25 | 26 | dir = normalizeDirection(dir) 27 | 28 | if (!value.startsWith('var(--') && ['top', 'left'].includes(dir ?? '')) { 29 | if (value.startsWith('-')) 30 | value = value.slice(1) 31 | else if (value !== '0') 32 | value = `-${value}` 33 | } 34 | 35 | return [value, dir] 36 | } 37 | -------------------------------------------------------------------------------- /src/theme.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@unocss/preset-mini' 2 | import { CSS_VARIABLE_PREFIX, ENTER_ANIMATION_NAME, EXIT_ANIMATION_NAME } from '@/constants' 3 | 4 | 5 | export const theme: Theme = { 6 | animation: { 7 | keyframes: { 8 | [ENTER_ANIMATION_NAME]: `{from{opacity:var(${CSS_VARIABLE_PREFIX}-enter-opacity,1);transform:translate3d(var(${CSS_VARIABLE_PREFIX}-enter-translate-x,0),var(${CSS_VARIABLE_PREFIX}-enter-translate-y,0),0) scale3d(var(${CSS_VARIABLE_PREFIX}-enter-scale,1),var(${CSS_VARIABLE_PREFIX}-enter-scale,1),var(${CSS_VARIABLE_PREFIX}-enter-scale,1)) rotate(var(${CSS_VARIABLE_PREFIX}-enter-rotate,0))}}`, 9 | [EXIT_ANIMATION_NAME]: `{to{opacity:var(${CSS_VARIABLE_PREFIX}-exit-opacity,1);transform:translate3d(var(${CSS_VARIABLE_PREFIX}-exit-translate-x,0),var(${CSS_VARIABLE_PREFIX}-exit-translate-y,0),0) scale3d(var(${CSS_VARIABLE_PREFIX}-exit-scale,1),var(${CSS_VARIABLE_PREFIX}-exit-scale,1),var(${CSS_VARIABLE_PREFIX}-exit-scale,1)) rotate(var(${CSS_VARIABLE_PREFIX}-exit-rotate,0))}}`, 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | permissions: 11 | id-token: write 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 20 | 21 | - name: Setup node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: lts/* 25 | cache: pnpm 26 | registry-url: 'https://registry.npmjs.org' 27 | 28 | - name: Generate changelog 29 | run: npx changelogithub 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Install Dependencies 34 | run: pnpm install 35 | 36 | - name: Build 37 | run: pnpm build 38 | 39 | - name: Publish to npm 40 | run: pnpm publish --access public --no-git-checks 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | NPM_CONFIG_PROVENANCE: true 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Aelita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 |

6 | 7 |

unocss-preset-animations

8 | 9 |

10 | 💅 An animation preset for UnoCSS, which adapts the tailwindcss-animate plugin 11 |

12 | 13 |

14 | NPM version

15 | 16 |
17 |

18 | 📚 Documentation 19 |

20 |
21 | 22 | ## Features 23 | 24 | - [Fade in / out](https://unocss-preset-animations.aelita.me/animations/fade.html) 25 | - [Zoom in / out](https://unocss-preset-animations.aelita.me/animations/zoom.html) 26 | - [Spin in / out](https://unocss-preset-animations.aelita.me/animations/spin.html) 27 | - [Slide in / out](https://unocss-preset-animations.aelita.me/animations/slide.html) 28 | - [Composable](https://unocss-preset-animations.aelita.me/animations/) 29 | 30 | 31 | ## License 32 | 33 | MIT License © 2023-present [Aelita (Tony Jiang)](https://aelita.me/) 34 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: Animations Preset for UnoCSS 6 | tagline: Simple · Beautiful · Flexible · Composable 7 | image: 8 | src: /logo.svg 9 | alt: UnoCSS 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /guide/ 14 | - theme: alt 15 | text: Animations 16 | link: /animations/ 17 | 18 | features: 19 | - title: Fade in / out 20 | icon: 21 | details: Define starting / ending opacity 22 | link: /animations/fade 23 | - title: Zoom in / out 24 | icon: 25 | details: Define starting / ending rotation 26 | link: /animations/zoom 27 | - title: Spin in / out 28 | icon: 29 | details: Define starting / ending scale 30 | link: /animations/spin 31 | - title: Slide in / out 32 | icon: 33 | details: Define starting / ending translate 34 | link: /animations/slide 35 | - title: Composable 36 | icon: 37 | details: Animations can be easily combined together 38 | link: /animations/ 39 | --- 40 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { definePreset } from '@unocss/core' 2 | import { rules } from '@/rules' 3 | import { shortcuts } from '@/shortcuts' 4 | import { theme } from '@/theme' 5 | 6 | 7 | export interface PresetAnimationsOptions { 8 | /** 9 | * The unit of time options 10 | * 11 | * @default 'ms' 12 | */ 13 | unit?: 's' | 'ms' 14 | 15 | /** 16 | * Default delay of animations 17 | */ 18 | delay?: number 19 | 20 | /** 21 | * Default direction of animations 22 | */ 23 | direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse' 24 | 25 | /** 26 | * Default duration of animations 27 | */ 28 | duration?: number 29 | 30 | /** 31 | * Default fill mode of animations 32 | */ 33 | fillMode?: 'none' | 'forwards' | 'backwards' | 'both' 34 | 35 | /** 36 | * Default iteration count of animations 37 | */ 38 | iterationCount?: number | 'infinite' 39 | 40 | /** 41 | * Default play state of animations 42 | */ 43 | playState?: 'running' | 'paused' 44 | 45 | /** 46 | * Default timing function of animations 47 | */ 48 | timingFunction?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | string 49 | } 50 | 51 | 52 | export const presetAnimations = definePreset((options: PresetAnimationsOptions = {}) => { 53 | options.unit = options.unit ?? 'ms' 54 | 55 | return { 56 | name: 'unocss-preset-animations', 57 | theme, 58 | layers: { 'una-base': -999 }, 59 | shortcuts: shortcuts(options), 60 | rules, 61 | } 62 | }) 63 | 64 | 65 | export default presetAnimations 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 New feature proposal 2 | description: Propose a new feature to be added to unocss-preset-animations 3 | labels: ['enhancement: pending triage'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: 'As a developer using unocss-preset-animations I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: We could provide following implementation... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Read the [documentation](https://unocss-preset-animations.aelita.me/) of using the package 40 | required: true 41 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate 42 | required: true 43 | -------------------------------------------------------------------------------- /docs/src/interactive-documentation.md: -------------------------------------------------------------------------------- 1 | # Interactive Documentation 2 | 3 | UnoCSS comes up with a Playground and an Interactive Documentation. 4 | 5 | This tutorial will guide you how to set it up and take advantage of them. 6 | 7 | 8 | ## Playground 9 | 10 | There is a playground that has already been set up for you to play with. 11 | 12 | You can access it [here](https://unocss.dev/play/?html=DwEwlgbgBAxgNgQwM5ILwCIAWBaJMBOApoQHZQDm%2BYIUADojIdmAC6EC2S2jJb%2B6APgBQUKKEixEKDAhJh2CNszJI41JmBLYW2AGYBXOHCi6EIDVoAMUWfMVMQ%2B-IrAB7LQEZL3m3IVLzRABPbABWHzYADx0ADki4QRFRKAAZV1cAaxsWKHZCJOAAenAIYSKS4SA&config=JYWwDg9gTgLgBAbwFBzgEwKYDNgDsMDCEuOA5gDQpxhQYDOGMAgjDFMAEYCuMwWAnpVQ16jAJIBjYnSHVaDGAFVcESgF84WKBBBwA5FxUS6dPUlCRYiOaOa5QAQ17S4GrTv2GIxugFoRCr4O9iBOwNJmSEgYAB6W8JhYDlwANgnYeITEZAAUyMLyjHQAXHAA2lQFtsoQOQCUslUKTCFh0nmawCkpALIQmKV6HBAwABZ6rg1UALrqdUhAA&css=PQKgBA6gTglgLgUzAYwK4Gc4HsC2YDCAyoWABYJQIA0YAhgHYAmYcUD6AZllDhWOqgAOg7nAB0YAGLcwCAB60cggDYIAXGBDAAUKDBi0mXGADe2sGC704AWgDuCGAHNScDQFYADJ4Dc5sAACtMLKAJ5gggCMLPK2ABR2pPBIcsoAlH4WAEa0yADWTlBYqEw2yFjK3Bpw5LxxAOTllVDoYpSMYgAs3vUZ2gC%2BmsBAA&options=N4IgLgTghgdgzgMwPYQLYAkyoDYgFwJTZwCmAvkA). 13 | 14 | 15 | ## Setup 16 | 17 | To set up the interactive documentation, follow these steps: 18 | 19 | - Copy the below code 20 | 21 | ```ts 22 | import { defineConfig, presetWind3 } from 'unocss' 23 | import { presetAnimations } from 'unocss-preset-animations' 24 | 25 | 26 | export default defineConfig({ 27 | presets: [ 28 | presetWind3(), 29 | presetAnimations({ 30 | fillMode: 'both', // or whatever options you want 31 | }), 32 | ], 33 | }) 34 | 35 | ``` 36 | 37 | - Head to the official [Interactive Documentation](https://unocss.dev/interactive/) 38 | - Click the icon in the top-right corner 39 | - Paste the above code into the editor and click `OK` 40 | - Enjoy 🥰 41 | -------------------------------------------------------------------------------- /test/layer.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { uno } from '~/utils' 3 | 4 | 5 | describe.concurrent('layer', () => { 6 | it('"animate-in" should be generated under "una-base" layer, and everything else to the "default" layer', async ({ expect }) => { 7 | const { css } = await uno.generate('animate-in fade-in-50') 8 | 9 | expect(css).toMatchInlineSnapshot(` 10 | "/* layer: una-base */ 11 | .animate-in{animation:una-in;animation-name:una-in;animation-duration:150ms;--una-enter-opacity:initial;--una-enter-scale:initial;--una-enter-rotate:initial;--una-enter-translate-x:initial;--una-enter-translate-y:initial;} 12 | /* layer: default */ 13 | @keyframes una-in{from{opacity:var(--una-enter-opacity,1);transform:translate3d(var(--una-enter-translate-x,0),var(--una-enter-translate-y,0),0) scale3d(var(--una-enter-scale,1),var(--una-enter-scale,1),var(--una-enter-scale,1)) rotate(var(--una-enter-rotate,0))}} 14 | .fade-in-50{--una-enter-opacity:0.5;}" 15 | `) 16 | }) 17 | 18 | 19 | it('"animate-out" should be generated under "una-base" layer, and everything else to the "default" layer', async ({ expect }) => { 20 | const { css } = await uno.generate('animate-out fade-out-50') 21 | 22 | expect(css).toMatchInlineSnapshot(` 23 | "/* layer: una-base */ 24 | .animate-out{animation:una-out;animation-name:una-out;animation-duration:150ms;--una-exit-opacity:initial;--una-exit-scale:initial;--una-exit-rotate:initial;--una-exit-translate-x:initial;--una-exit-translate-y:initial;} 25 | /* layer: default */ 26 | @keyframes una-out{to{opacity:var(--una-exit-opacity,1);transform:translate3d(var(--una-exit-translate-x,0),var(--una-exit-translate-y,0),0) scale3d(var(--una-exit-scale,1),var(--una-exit-scale,1),var(--una-exit-scale,1)) rotate(var(--una-exit-rotate,0))}} 27 | .fade-out-50{--una-exit-opacity:0.5;}" 28 | `) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /docs/.vitepress/plugins/externalLinkIcon.ts: -------------------------------------------------------------------------------- 1 | import type MarkdownIt from 'markdown-it' 2 | import type { RenderRule } from 'markdown-it/lib/renderer.mjs' 3 | 4 | 5 | const externalIcon = ` 6 | 7 | 8 | 9 | ` 10 | 11 | 12 | function externalLinkIcon(md: MarkdownIt): void { 13 | const renderToken: RenderRule = (tokens, idx, options, env, self) => 14 | self.renderToken(tokens, idx, options) 15 | 16 | const defaultLinkOpenRenderer = md.renderer.rules.link_open ?? renderToken 17 | const defaultLinkCloseRenderer = md.renderer.rules.link_close ?? renderToken 18 | 19 | let isExternalLink = false 20 | 21 | md.renderer.rules.link_open = (tokens, idx, options, env, self): string => { 22 | const token = tokens[idx] 23 | const href = token?.attrGet('href') 24 | 25 | if (href && /^((ht|f)tps?):\/\/?/.test(href)) { 26 | isExternalLink = true 27 | token?.attrSet('style', 'display: inline-flex; align-items: center; column-gap: .3em;') 28 | } 29 | 30 | return defaultLinkOpenRenderer(tokens, idx, options, env, self) 31 | } 32 | 33 | md.renderer.rules.link_close = ( 34 | tokens, 35 | idx, 36 | options, 37 | env, 38 | self, 39 | ): string => { 40 | if (isExternalLink) { 41 | isExternalLink = false 42 | return `${externalIcon}${self.renderToken(tokens, idx, options)}` 43 | } 44 | 45 | return defaultLinkCloseRenderer(tokens, idx, options, env, self) 46 | } 47 | } 48 | 49 | 50 | export default externalLinkIcon 51 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress site to Pages 2 | 3 | on: 4 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 5 | # using the `master` branch as the default branch. 6 | push: 7 | branches: [main] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: pages 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 33 | - uses: pnpm/action-setup@v3 # Uncomment this if you're using pnpm 34 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 35 | - name: Setup Node 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: lts/* 39 | cache: pnpm 40 | - name: Setup Pages 41 | uses: actions/configure-pages@v4 42 | - name: Install dependencies 43 | run: pnpm install # or pnpm install / yarn install / bun install 44 | - name: Build project 45 | run: pnpm build 46 | - name: Build with VitePress 47 | run: | 48 | pnpm docs:build 49 | touch docs/.vitepress/dist/.nojekyll 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: docs/.vitepress/dist 54 | 55 | # Deployment job 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | needs: build 61 | runs-on: ubuntu-latest 62 | name: Deploy 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /docs/src/animations/index.md: -------------------------------------------------------------------------------- 1 | # Basic Usage 2 | 3 | In order to use animations provided by this preset, you need to add the `animate-in` or `animate-out` classname for enter or exit animations correspondingly, then add animations you need. 4 | 5 | ::: tip 6 | Different animations ([`fade`](./fade), [`zoom`](./zoom), [`spin`](./spin) and [`slide`](./slide)) can be used together. 7 | 8 | E.g. the below code will make the element fade in, zoom in and slide in from top at the same time. 9 | 10 | ```html 11 | 12 | ``` 13 | ::: 14 | 15 | ::: warning 16 | Usage of the bracket `[]` syntax is dropped in this preset from **v1.0.0-beta.8**. 17 | 18 | This is **intentional** because 19 | - UnoCSS is very flexible, and you don't need to use it at all in this preset. 20 | - The syntax is error-prone and may destroy the whole animation with a single invalid value. 21 | 22 | For anything you define in `[]` except specific syntax for CSS variables (e.g. `[--foo]`), it will always use the value inside as is, hence it's very easy to make mistakes. 23 | 24 | E.g. the below code will destroy the whole animation since it's not a valid `rotate()` value. 25 | ```html 26 | 27 | ``` 28 | It will generate `--una-enter-rotate: 30;`, which lacks a unit and will make the whole `transform` property invalid. 29 | 30 | Instead, just write `spin-in-30` or `spin-in-30deg` to make it work, which will generate `--una-enter-rotate: 30deg;`. 31 | ::: 32 | 33 | ## Enter Animations 34 | 35 | To give an element enter animations, use the `animate-in` shortcut in combination with [`fade-in`](./fade#fade-in), [`zoom-in`](./zoom#zoom-in), [`spin-in`](./spin#spin-in) and [`slide-in`](./slide#slide-in) classnames. 36 | 37 | ```html 38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | ## Exit Animations 45 | 46 | To give an element exit animations, use the `animate-out` shortcut in combination with [`fade-out`](./fade#fade-out), [`zoom-out`](./zoom#zoom-out), [`spin-out`](./spin#spin-out) and [`slide-out`](./slide#slide-out) classnames. 47 | 48 | ```html 49 | 50 | 51 | 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /src/shortcuts.ts: -------------------------------------------------------------------------------- 1 | import type { PresetAnimationsOptions } from '@/index' 2 | import type { Theme } from '@unocss/preset-mini' 3 | import type { CSSObject, UserShortcuts } from 'unocss' 4 | import { CSS_VARIABLE_PREFIX, ENTER_ANIMATION_NAME, EXIT_ANIMATION_NAME } from '@/constants' 5 | 6 | 7 | export function shortcuts(options: PresetAnimationsOptions): UserShortcuts { 8 | const { unit = 'ms' } = options 9 | 10 | 11 | const getSharedAnimationProperties = (theme: Theme): CSSObject => ({ 12 | // theme.duration?.DEFAULT is undefined if `presetWind4.preflights.theme.mode` is 'on-demand' by default 13 | // so we fallback to '150ms' if theme.duration?.DEFAULT is undefined 14 | 'animation-duration': options.duration ? `${options.duration}${unit}` : theme.duration?.DEFAULT || '150ms', 15 | ...options.delay && { 'animation-delay': `${options.delay}${unit}` }, 16 | ...options.direction && { 'animation-direction': options.direction }, 17 | ...options.fillMode && { 'animation-fill-mode': options.fillMode }, 18 | ...options.iterationCount && { 'animation-iteration-count': options.iterationCount }, 19 | ...options.playState && { 'animation-play-state': options.playState }, 20 | ...options.timingFunction && { 'animation-timing-function': options.timingFunction }, 21 | }) 22 | 23 | 24 | return [ 25 | [ 26 | /^animate-in$/, 27 | (_, { theme }) => [ 28 | `keyframes-${ENTER_ANIMATION_NAME}`, 29 | { 30 | 'animation-name': ENTER_ANIMATION_NAME, 31 | ...getSharedAnimationProperties(theme), 32 | [`${CSS_VARIABLE_PREFIX}-enter-opacity`]: 'initial', 33 | [`${CSS_VARIABLE_PREFIX}-enter-scale`]: 'initial', 34 | [`${CSS_VARIABLE_PREFIX}-enter-rotate`]: 'initial', 35 | [`${CSS_VARIABLE_PREFIX}-enter-translate-x`]: 'initial', 36 | [`${CSS_VARIABLE_PREFIX}-enter-translate-y`]: 'initial', 37 | }, 38 | ], 39 | { layer: 'una-base', autocomplete: 'animate-in' }, 40 | ], 41 | [ 42 | /^animate-out$/, 43 | (_, { theme }) => [ 44 | `keyframes-${EXIT_ANIMATION_NAME}`, 45 | { 46 | 'animation-name': EXIT_ANIMATION_NAME, 47 | ...getSharedAnimationProperties(theme), 48 | [`${CSS_VARIABLE_PREFIX}-exit-opacity`]: 'initial', 49 | [`${CSS_VARIABLE_PREFIX}-exit-scale`]: 'initial', 50 | [`${CSS_VARIABLE_PREFIX}-exit-rotate`]: 'initial', 51 | [`${CSS_VARIABLE_PREFIX}-exit-translate-x`]: 'initial', 52 | [`${CSS_VARIABLE_PREFIX}-exit-translate-y`]: 'initial', 53 | }, 54 | ], 55 | { layer: 'una-base', autocomplete: 'animate-out' }, 56 | ], 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/style.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand-1: #ff7189; 3 | --vp-c-brand-2: #ff879a; 4 | --vp-c-brand-3: #ff9eac; 5 | --vp-c-brand-soft: rgb(255 158 172 / 0.14); 6 | } 7 | 8 | :root.dark { 9 | --vp-c-brand-1: #ffb7c2; 10 | --vp-c-brand-2: #ff9eac; 11 | --vp-c-brand-3: #ff879a; 12 | --vp-c-brand-soft: rgb(255 158 172 / 0.14); 13 | } 14 | 15 | 16 | /** 17 | * Component: Home 18 | * -------------------------------------------------------------------------- */ 19 | 20 | :root { 21 | --vp-home-hero-name-color: transparent; 22 | --vp-home-hero-name-background: linear-gradient(135deg, #ff9eac 30%, #5effff 60%, #5effff); 23 | --vp-home-hero-image-background-image: linear-gradient(135deg, #ff9eac 35%, #5effff); 24 | --vp-home-hero-image-filter: blur(72px); 25 | } 26 | 27 | :root.dark { 28 | --vp-home-hero-name-background: linear-gradient(135deg, #ffb7c2 30%, #9dffff 60%, #9dffff); 29 | --vp-home-hero-image-background-image: linear-gradient(135deg, #ffb7c2 35%, #9dffff); 30 | } 31 | 32 | .VPHero .image-bg { 33 | @apply opacity-80 z-1; 34 | } 35 | 36 | .VPFeatures { 37 | .VPFeature { 38 | @apply transition-all transition-duration-800; 39 | } 40 | 41 | .item:nth-child(1) .VPFeature { 42 | @apply @hover:opacity-30; 43 | } 44 | 45 | .item:nth-child(2) .VPFeature { 46 | @apply @hover:scale-130; 47 | } 48 | 49 | .item:nth-child(3) .VPFeature { 50 | @apply @hover:rotate-30; 51 | } 52 | 53 | .item:nth-child(4) .VPFeature { 54 | @apply @hover:-translate-y-30%; 55 | } 56 | 57 | .item:nth-child(5) .VPFeature { 58 | @apply @hover:(opacity-50 scale-130 rotate-30 -translate-y-30% translate-x-50%); 59 | } 60 | } 61 | 62 | 63 | /** 64 | * Component: Custom Block 65 | * -------------------------------------------------------------------------- */ 66 | 67 | :root { 68 | --vp-custom-block-tip-border: transparent; 69 | --vp-custom-block-tip-text: var(--vp-c-text-1); 70 | --vp-custom-block-tip-bg: var(--vp-c-green-soft); 71 | --vp-custom-block-tip-code-bg: var(--vp-c-green-soft); 72 | } 73 | 74 | .custom-block.tip a, 75 | .custom-block.tip code { 76 | color: var(--vp-c-green-1); 77 | } 78 | 79 | .custom-block.tip a:hover { 80 | color: var(--vp-c-green-2); 81 | } 82 | 83 | 84 | /** 85 | * Component: Algolia 86 | * -------------------------------------------------------------------------- */ 87 | 88 | .DocSearch { 89 | --docsearch-primary-color: var(--vp-c-brand-1) !important; 90 | } 91 | 92 | 93 | /** 94 | * Component: Docs 95 | */ 96 | .vp-doc a { 97 | @apply decoration-dotted; 98 | } 99 | 100 | .custom-block a:focus,.custom-block a:active,.custom-block a:hover,.vp-doc a:focus,.vp-doc a:active,.vp-doc a:hover { 101 | @apply decoration-solid; 102 | } 103 | -------------------------------------------------------------------------------- /docs/src/guide/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: [2, 3] 3 | --- 4 | 5 | 6 | # Getting Started 7 | 8 | 9 | ## Overview 10 | 11 | This is a preset of [UnoCSS](https://unocss.dev/) for creating beautiful animations with simple classnames. 12 | 13 | It adapts the [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate) TailwindCSS plugin to be compatible with UnoCSS but more flexible and user-friendly. 14 | 15 | 16 | ## Installation 17 | 18 | ::: code-group 19 | ```bash [npm] 20 | npm install -D unocss-preset-animations 21 | ``` 22 | ```bash [yarn] 23 | yarn add -D unocss-preset-animations 24 | ``` 25 | ```bash [pnpm] 26 | pnpm add -D unocss-preset-animations 27 | ``` 28 | ```bash [bun] 29 | bun add -D unocss-preset-animations 30 | ``` 31 | ::: 32 | 33 | ::: danger 34 | This preset requires `unocss` version of **v0.56.0+** 35 | ::: 36 | 37 | ::: warning 38 | This preset is based on `@unocss/preset-wind3`, please make sure it is included in the `presets`, otherwise it won't work as expected. 39 | ::: 40 | 41 | ::: tip 42 | This preset is compatible with `@unocss/preset-wind4`, but `@unocss/preset-wind3` is recommended for better compatibility. 43 | 44 | This preset will adapt `tw-animate-css` in `v2` to provide better integration with `@unocss/preset-wind4` soon. 45 | ::: 46 | 47 | 48 | ## Usage 49 | 50 | In your UnoCSS config, add this preset to the `presets` option: 51 | 52 | ```ts 53 | import { defineConfig, presetWind3 } from 'unocss' // v0.56.0 or above is required [!code focus:3] 54 | import { presetAnimations } from 'unocss-preset-animations' // [!code ++] 55 | import type { Theme } from '@unocss/preset-wind3' 56 | // This can also be imported from '@unocss/preset-mini', or from 'unocss': 57 | import type { PresetWind3Theme as Theme } from 'unocss' 58 | // [!code focus:999] 59 | 60 | export default defineConfig({ 61 | presets: [ 62 | presetWind3(), 63 | presetAnimations(/* options */), // [!code ++] 64 | ] 65 | }) 66 | ``` 67 | 68 | 69 | ## Options 70 | 71 | All properties are optional. 72 | 73 | ### unit 74 | 75 | - Type: `'ms' | 's'` 76 | - Default: `'ms'` 77 | 78 | The unit of time options (duration and delay). 79 | 80 | ### delay 81 | 82 | - Type: `number` 83 | 84 | Default delay time for animations. 85 | 86 | ### direction 87 | 88 | - Type: `'normal' | 'reverse' | 'alternate' | 'alternate-reverse'` 89 | 90 | Default direction of animations. 91 | 92 | ### duration 93 | 94 | - Type: `number` 95 | - Default: `theme.duration.DEFAULT` (`150ms` if unchanged) 96 | 97 | Default duration time for animations. 98 | 99 | ### fillMode 100 | 101 | - Type: `'none' | 'forwards' | 'backwards' | 'both'` 102 | 103 | Default fill mode for animations. `both` is generally useful. 104 | 105 | ### iterationCount 106 | 107 | - Type: `number | 'infinite'` 108 | 109 | Default iteration count for animations. 110 | 111 | ### playState 112 | 113 | - Type: `'running' | 'paused'` 114 | 115 | Default play state for animations. 116 | 117 | ### timingFunction 118 | 119 | - Type: `'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | string` 120 | 121 | Default timing function for animations. 122 | -------------------------------------------------------------------------------- /docs/src/animations/spin.md: -------------------------------------------------------------------------------- 1 | # Spin Animations 2 | 3 | Use classname `spin-(in|out)-` to define animation's `rotate`. 4 | 5 | - The `-` part is ***optional***. 6 | 7 |
8 | 9 | You can use either of below as the ``: 10 | 11 | - Any number including negative and decimals. E.g. `spin-in-50` 12 | - Number followed by one of the unit: `deg`, `rad`, `grad` or `turn`. E.g. `spin-in-3.1416rad` 13 | - CSS variable. E.g. `spin-in-$my-css-var` 14 | 15 | ## Spin In 16 | 17 | Define enter animation's starting `rotate`. 18 | 19 | - The default **value** is `30deg` if not specified. 20 | - The default **unit** is `deg` if not specified. 21 | 22 | ```html 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | | Classname | Property | 30 | |-----------------------|------------------------------------------| 31 | | `spin-in` | `--una-enter-rotate: 30deg;` | 32 | | `spin-in-0` | `--una-enter-rotate: 0;` | 33 | | `spin-in-10` | `--una-enter-rotate: 10deg;` | 34 | | `spin-in-.8` | `--una-enter-rotate: 0.8deg;` | 35 | | `spin-in-52.1` | `--una-enter-rotate: 52.1deg;` | 36 | | `spin-in--20` | `--una-enter-rotate: -20deg;` | 37 | | `spin-in--66.66` | `--una-enter-rotate: -66.66deg;` | 38 | | `spin-in-3.1416rad` | `--una-enter-rotate: 3.1416rad;` | 39 | | `spin-in--170grad` | `--una-enter-rotate: -170grad;` | 40 | | `spin-in-0.6turn` | `--una-enter-rotate: 0.6turn;` | 41 | | `spin-in-$my-css-var` | `--una-enter-rotate: var(--my-css-var);` | 42 | 43 | ## Spin Out 44 | 45 | Define exit animation's ending `rotate`. 46 | 47 | - The default **value** is `30deg` if not specified. 48 | - The default **unit** is `deg` if not specified. 49 | 50 | ```html 51 | 52 | 53 | 54 | 55 | ``` 56 | 57 | | Classname | Property | 58 | |------------------------|-----------------------------------------| 59 | | `spin-out` | `--una-exit-rotate: 30deg;` | 60 | | `spin-out-0` | `--una-exit-rotate: 0;` | 61 | | `spin-out-10` | `--una-exit-rotate: 10deg;` | 62 | | `spin-out-.8` | `--una-exit-rotate: 0.8deg;` | 63 | | `spin-out-52.1` | `--una-exit-rotate: 52.1deg;` | 64 | | `spin-out--20` | `--una-exit-rotate: -20deg;` | 65 | | `spin-out--66.66` | `--una-exit-rotate: -66.66deg;` | 66 | | `spin-out-3.1416rad` | `--una-exit-rotate: 3.1416rad;` | 67 | | `spin-out--170grad` | `--una-exit-rotate: -170grad;` | 68 | | `spin-out-0.6turn` | `--una-exit-rotate: 0.6turn;` | 69 | | `spin-out-$my-css-var` | `--una-exit-rotate: var(--my-css-var);` | 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report an issue with unocss-preset-animations 3 | labels: [pending triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! If you have a usage question 9 | or are unsure if this is really a bug, make sure to: 10 | 11 | - Read the [documentation](https://unocss-preset-animations.aelita.me/) of using the package 12 | - Ask on [GitHub Discussion](https://github.com/xsjcTony/unocss-preset-animations/discussions) 13 | 14 | - type: input 15 | id: version 16 | attributes: 17 | label: unocss-preset-animations version 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: bug-description 22 | attributes: 23 | label: Describe the bug 24 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 25 | placeholder: Bug description 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: reproduction 30 | attributes: 31 | label: Reproduction 32 | description: Please provide a link to [StackBlitz](https://stackblitz.com/edit/vitejs-vite-fbm8l5qh?file=src%2FApp.tsx), [unocss playground](https://unocss.dev/play/#html=DwEwlgbgBAxgNgQwM5ILwCIAWBaJMBOApoQHZQDm%2BYIUADojIdmAC6EC2S2jJb%2B6APgBQUKKEixEKDAhJh2CNszJI41JmBLYW2AGYBXOHCi6EIDVoAMUWfMVMQ%2B-IrAB7LQEZL3m3IVLzRABPbABWHzYADx0ADki4QRFRKAAZV1cAaxsWKHZCJOAAenAIYSKS4SA&config=JYWwDg9gTgLgBAbwFBzgEwKYDNgDsMDCEuOA5gDQpxhQYDOGMA6nmgMyUC%2BcWUEIcAOQBXXBADGdOoKShIsRNVoMYAQVygAhjGDE6cbr35DREqQFoa9Ruc0aQ23bmlJXGAB7z4mLJuEAbb2w8QmIyAApkVCsVOgAuOABtKmjlRhZcdnCASkpUVOs1e0c9SJ5gf38AWQhMBMEAIwgYAAtBA1yqAF0ubKQgA&css=PQKgBA6gTglgLgUzAYwK4Gc4HsC2YDCAyoWABYJQIA0YAhgHYAmYcUD6AZllDhWOqgAOg7nAB0YAGLcwCAB60cggDYIAXGBDAAUKDBi0mXGADe2sGC704AWgDuCGAHNScDQFYADJ4Dc5sAACtMLKAJ5gggCMLPK2ABR2pPBIcsoAlH4WAEa0yADWTlBYqEw2yFjK3Bpw5LxxAOTllVDoYpSMYgAs3vUZ2gC%2BmsBAA&options=N4IgLgTghgdgzgMwPYQLYAkyoDYgFwJTZwCmAvkA&version=66.0.0) or a github repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. [Why reproduction is required](https://antfu.me/posts/why-reproductions-are-required). 33 | placeholder: Reproduction 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: system-info 38 | attributes: 39 | label: System Info 40 | placeholder: System, Browsers, Framework 41 | validations: 42 | required: false 43 | - type: checkboxes 44 | id: checkboxes 45 | attributes: 46 | label: Validations 47 | description: Before submitting the issue, please make sure you do the following 48 | options: 49 | - label: Check that there isn't [already an issue](https://github.com/xsjcTony/unocss-preset-animations/issues) that reports the same bug to avoid creating a duplicate 50 | required: true 51 | - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/xsjcTony/unocss-preset-animations/discussions) 52 | required: true 53 | - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug 54 | required: true 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unocss-preset-animations", 3 | "version": "1.3.0", 4 | "description": "💅 An animation preset for UnoCSS, which adapts the tailwindcss-animate plugin", 5 | "packageManager": "pnpm@10.18.2", 6 | "type": "module", 7 | "author": "Aelita ", 8 | "license": "MIT", 9 | "homepage": "https://unocss-preset-animations.aelita.me/", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/xsjcTony/unocss-preset-animations" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/xsjcTony/unocss-preset-animations/issues" 16 | }, 17 | "keywords": [ 18 | "css", 19 | "unocss", 20 | "unocss-preset", 21 | "unocss-preset-animations", 22 | "tailwind-animate" 23 | ], 24 | "sideEffects": false, 25 | "exports": { 26 | ".": { 27 | "import": { 28 | "types": "./dist/index.d.mts", 29 | "default": "./dist/index.mjs" 30 | }, 31 | "require": { 32 | "types": "./dist/index.d.cts", 33 | "default": "./dist/index.cjs" 34 | } 35 | } 36 | }, 37 | "main": "dist/index.cjs", 38 | "module": "dist/index.mjs", 39 | "types": "dist/index.d.ts", 40 | "files": [ 41 | "dist" 42 | ], 43 | "scripts": { 44 | "build": "unbuild", 45 | "release": "pnpm test:ci && bumpp", 46 | "typecheck": "vue-tsc", 47 | "lint": "eslint .", 48 | "lint:fix": "eslint --fix .", 49 | "test": "vitest", 50 | "test:coverage": "vitest run --coverage", 51 | "test:update": "vitest run -u", 52 | "test:ci": "pnpm typecheck && pnpm lint && pnpm test run && pnpm build", 53 | "docs:dev": "vitepress dev docs", 54 | "docs:build": "vitepress build docs", 55 | "docs:preview": "vitepress preview docs" 56 | }, 57 | "simple-git-hooks": { 58 | "pre-commit": "pnpm lint-staged" 59 | }, 60 | "lint-staged": { 61 | "*": "pnpm lint" 62 | }, 63 | "devDependencies": { 64 | "@aelita-dev/eslint-config": "3.27.0", 65 | "@iconify/json": "^2.2.395", 66 | "@types/dom-view-transitions": "^1.0.6", 67 | "@types/markdown-it": "^14.1.2", 68 | "@types/node": "^20.19.21", 69 | "@unocss/core": "^66.5.3", 70 | "@unocss/eslint-plugin": "^66.5.3", 71 | "@unocss/preset-mini": "^66.5.3", 72 | "@vitest/coverage-v8": "^3.2.4", 73 | "@vitest/eslint-plugin": "^1.3.17", 74 | "@vue/language-server": "^3.1.1", 75 | "bumpp": "^10.3.1", 76 | "bundle-require": "^5.1.0", 77 | "changelogithub": "^13.16.0", 78 | "eslint": "^9.37.0", 79 | "eslint-import-resolver-typescript": "^4.4.4", 80 | "eslint-plugin-import-x": "^4.16.1", 81 | "eslint-plugin-vue": "^10.5.0", 82 | "eslint-plugin-vuejs-accessibility": "^2.4.1", 83 | "eslint-processor-vue-blocks": "^2.0.0", 84 | "lint-staged": "^16.2.4", 85 | "markdown-it": "^14.1.0", 86 | "sass-embedded": "^1.93.2", 87 | "simple-git-hooks": "^2.13.1", 88 | "typescript": "~5.9.3", 89 | "unbuild": "3.6.1", 90 | "unocss": "^66.5.3", 91 | "vite-tsconfig-paths": "^5.1.4", 92 | "vitepress": "1.6.4", 93 | "vitest": "^3.2.4", 94 | "vue": "^3.5.22", 95 | "vue-eslint-parser": "^10.2.0", 96 | "vue-tsc": "^3.1.1" 97 | }, 98 | "peerDependencies": { 99 | "@unocss/preset-wind3": ">=0.56.0 < 101", 100 | "unocss": ">=0.56.0 < 101" 101 | }, 102 | "peerDependenciesMeta": { 103 | "@unocss/preset-wind3": { 104 | "optional": true 105 | } 106 | }, 107 | "pnpm": { 108 | "neverBuiltDependencies": [] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /docs/src/animations/animation-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: [2, 3] 3 | next: Animations 4 | --- 5 | 6 | # Animation Properties 7 | 8 | Animation property modifiers are supported by `@unocss/preset-wind3`. 9 | 10 | ## `animation-delay` 11 | 12 | ```html 13 | 14 | 15 | 16 | ``` 17 | 18 | ## `animation-direction` 19 | 20 | ```html 21 | 22 | 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | ## `animation-duration` 30 | 31 | ```html 32 | 33 | 34 | 35 | ``` 36 | 37 | ::: tip 38 | If no `animation-duration` is defined, it will fall back to `theme.duration.DEFAULT`. 39 | 40 | The value is `150ms` by default if unchanged, see [unocss/packages/preset-mini/src/_theme/misc.ts at main · unocss/unocss](https://github.com/unocss/unocss/blob/efdc358897a308323e1d943dd0f7c13e344e1495/packages/preset-mini/src/_theme/misc.ts#L37) 41 | ::: 42 | 43 | ## `animation-fill-mode` 44 | 45 | ```html 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | ## `animation-iteration-count` 57 | 58 | ```html 59 | 60 | 61 | 62 | 63 | 64 | 65 | ``` 66 | 67 | ## `animation-play-state` 68 | 69 | ```html 70 | 71 | 72 | 73 | 74 | 75 | 76 | ``` 77 | 78 | ## `animation-timing-function` 79 | 80 | ```html 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | ## Prefers reduced motion 88 | 89 | ```html 90 | 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/src/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Created with Fabric.js 3.5.0 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/rules.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '@unocss/preset-mini' 2 | import type { Rule } from 'unocss' 3 | import { h } from '@unocss/preset-mini/utils' 4 | import { CSS_VARIABLE_PREFIX } from '@/constants' 5 | import { handleSlide } from '@/utils' 6 | 7 | 8 | const DEFAULT_FADE_OPACITY = '0' 9 | const DEFAULT_ZOOM_SCALE = '0' 10 | const DEFAULT_SPIN_DEGREE = '30deg' 11 | export const DEFAULT_SLIDE_TRANSLATE = '100%' 12 | 13 | 14 | const DIRECTIONS_AUTOCOMPLETE = '(t|b|l|r|top|bottom|left|right)' 15 | 16 | 17 | const fadeRules: Rule[] = [ 18 | [ 19 | /^fade-in(?:-(.+))?$/, 20 | ([, op]) => ({ 21 | [`${CSS_VARIABLE_PREFIX}-enter-opacity`]: h.cssvar.percent(op || DEFAULT_FADE_OPACITY), 22 | }), 23 | { autocomplete: 'fade-(in|out)-' }, 24 | ], 25 | [ 26 | /^fade-out(?:-(.+))?$/, 27 | ([, op]) => ({ 28 | [`${CSS_VARIABLE_PREFIX}-exit-opacity`]: h.cssvar.percent(op || DEFAULT_FADE_OPACITY), 29 | }), 30 | ], 31 | ] 32 | 33 | 34 | const zoomRules: Rule[] = [ 35 | [ 36 | /^zoom-in(?:-(.+))?$/, 37 | ([, scale]) => ({ 38 | [`${CSS_VARIABLE_PREFIX}-enter-scale`]: h.cssvar.fraction.percent(scale || DEFAULT_ZOOM_SCALE), 39 | }), 40 | { autocomplete: 'zoom-(in|out)-' }, 41 | ], 42 | [ 43 | /^zoom-out(?:-(.+))?$/, 44 | ([, scale]) => ({ 45 | [`${CSS_VARIABLE_PREFIX}-exit-scale`]: h.cssvar.fraction.percent(scale || DEFAULT_ZOOM_SCALE), 46 | }), 47 | ], 48 | ] 49 | 50 | 51 | const spinRules: Rule[] = [ 52 | [ 53 | /^spin-in(?:-(.+))?$/, 54 | ([, deg]) => ({ 55 | [`${CSS_VARIABLE_PREFIX}-enter-rotate`]: h.cssvar.degree(deg || DEFAULT_SPIN_DEGREE), 56 | }), 57 | { autocomplete: 'spin-(in|out)-' }, 58 | ], 59 | [ 60 | /^spin-out(?:-(.+))?$/, 61 | ([, deg]) => ({ 62 | [`${CSS_VARIABLE_PREFIX}-exit-rotate`]: h.cssvar.degree(deg || DEFAULT_SPIN_DEGREE), 63 | }), 64 | ], 65 | ] 66 | 67 | 68 | const slideRules: Rule[] = [ 69 | [ 70 | /^slide-in(?:-from)?-(t|b|l|r|top|bottom|left|right)(?:-(.+))?$/, 71 | ([, dir, val]) => { 72 | const [value, direction] = handleSlide(val, dir) 73 | 74 | if (!value) 75 | return 76 | 77 | switch (direction) { 78 | case 'top': 79 | case 'bottom': { 80 | return { [`${CSS_VARIABLE_PREFIX}-enter-translate-y`]: value } 81 | } 82 | case 'left': 83 | case 'right': { 84 | return { [`${CSS_VARIABLE_PREFIX}-enter-translate-x`]: value } 85 | } 86 | default: { 87 | return 88 | } 89 | } 90 | }, 91 | { 92 | autocomplete: [ 93 | `slide-(in|out)-${DIRECTIONS_AUTOCOMPLETE}-`, 94 | `slide-(in|out)-${DIRECTIONS_AUTOCOMPLETE}-full`, 95 | `slide-in-from-${DIRECTIONS_AUTOCOMPLETE}-`, 96 | `slide-in-from-${DIRECTIONS_AUTOCOMPLETE}-full`, 97 | ], 98 | }, 99 | ], 100 | 101 | [ 102 | /^slide-out(?:-to)?-(t|b|l|r|top|bottom|left|right)(?:-(.+))?$/, 103 | ([, dir, val]) => { 104 | const [value, direction] = handleSlide(val, dir) 105 | 106 | if (!value) 107 | return 108 | 109 | switch (direction) { 110 | case 'top': 111 | case 'bottom': { 112 | return { [`${CSS_VARIABLE_PREFIX}-exit-translate-y`]: value } 113 | } 114 | case 'left': 115 | case 'right': { 116 | return { [`${CSS_VARIABLE_PREFIX}-exit-translate-x`]: value } 117 | } 118 | default: { 119 | return 120 | } 121 | } 122 | }, 123 | { 124 | autocomplete: [ 125 | `slide-out-to-${DIRECTIONS_AUTOCOMPLETE}-`, 126 | `slide-out-to-${DIRECTIONS_AUTOCOMPLETE}-full`, 127 | ], 128 | }, 129 | ], 130 | ] 131 | 132 | 133 | export const rules: Rule[] = [ 134 | ...fadeRules, 135 | ...zoomRules, 136 | ...spinRules, 137 | ...slideRules, 138 | ] 139 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import unocss from 'unocss/vite' 2 | import tsconfigPaths from 'vite-tsconfig-paths' 3 | import { defineConfig } from 'vitepress' 4 | import { version } from '../../package.json' 5 | import externalLinkIcon from './plugins/externalLinkIcon' 6 | 7 | 8 | export default defineConfig({ 9 | srcDir: './src', 10 | 11 | vite: { 12 | plugins: [ 13 | // @ts-expect-error - wait for VitePress 2 to support Vite 6 14 | unocss(), 15 | // @ts-expect-error - wait for VitePress 2 to support Vite 6 16 | tsconfigPaths(), 17 | ], 18 | }, 19 | 20 | lastUpdated: true, 21 | lang: 'en-US', 22 | appearance: 'dark', 23 | title: 'unocss-preset-animations', 24 | titleTemplate: ':title | unocss-preset-animations', 25 | head: [ 26 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], 27 | ], 28 | description: '💅 An adaptation of the tailwindcss-animate Tailwind plugin for UnoCSS', 29 | markdown: { 30 | theme: { 31 | dark: 'material-theme-palenight', 32 | light: 'vitesse-light', 33 | }, 34 | config: (md) => { 35 | md.use(externalLinkIcon) 36 | }, 37 | }, 38 | themeConfig: { 39 | logo: '/logo.svg', 40 | siteTitle: 'Animations Preset', 41 | 42 | editLink: { 43 | pattern: 'https://github.com/xsjcTony/unocss-preset-animations/edit/main/docs/src/:path', 44 | text: 'Suggest changes to this page', 45 | }, 46 | 47 | search: { 48 | provider: 'algolia', 49 | options: { 50 | appId: '59LIXN9T8K', 51 | apiKey: 'c029fa22a73158f7b77eab5f47a4f33a', 52 | indexName: 'unocss-preset-animations-aelita', 53 | }, 54 | }, 55 | 56 | footer: { 57 | message: 'Released under the MIT License.', 58 | copyright: 'Copyright © 2023-present Aelita (Tony Jiang)', 59 | }, 60 | 61 | nav: [ 62 | { text: 'Home', link: '/' }, 63 | { text: 'Guide', link: '/guide/', activeMatch: '^/guide/' }, 64 | { text: 'Animations', link: '/animations/', activeMatch: '^/animations/' }, 65 | { text: 'Playground', link: 'https://unocss.dev/play/?html=DwEwlgbgBAxgNgQwM5ILwCIAWBaJMBOApoQHZQDm%2BYIUADojIdmAC6EC2S2jJb%2B6APgBQUKKEixEKDAhJh2CNszJI41JmBLYW2AGYBXOHCi6EIDVoAMUWfMVMQ%2B-IrAB7LQEZL3m3IVLzRABPbABWHzYADx0ADki4QRFRKAAZV1cAaxsWKHZCJOAAenAIYSKS4SA&config=JYWwDg9gTgLgBAbwFBzgEwKYDNgDsMDCEuOA5gDQpxhQYDOGMAgjDFMAEYCuMwWAnpVQ16jAJIBjYnSHVaDGAFVcESgF84WKBBBwA5FxUS6dPUlCRYiOaOa5QAQ17S4GrTv2GIxugFoRCr4O9iBOwNJmSEgYAB6W8JhYDlwANgnYeITEZAAUyMLyjHQAXHAA2lQFtsoQOQCUslUKTCFh0nmawCkpALIQmKV6HBAwABZ6rg1UALrqdUhAA&css=PQKgBA6gTglgLgUzAYwK4Gc4HsC2YDCAyoWABYJQIA0YAhgHYAmYcUD6AZllDhWOqgAOg7nAB0YAGLcwCAB60cggDYIAXGBDAAUKDBi0mXGADe2sGC704AWgDuCGAHNScDQFYADJ4Dc5sAACtMLKAJ5gggCMLPK2ABR2pPBIcsoAlH4WAEa0yADWTlBYqEw2yFjK3Bpw5LxxAOTllVDoYpSMYgAs3vUZ2gC%2BmsBAA&options=N4IgLgTghgdgzgMwPYQLYAkyoDYgFwJTZwCmAvkA' }, 66 | { 67 | text: `v${version}`, 68 | items: [ 69 | { text: 'Release Notes', link: 'https://github.com/xsjcTony/unocss-preset-animations/releases' }, 70 | ], 71 | }, 72 | ], 73 | 74 | sidebar: [ 75 | { 76 | text: 'Guide', 77 | items: [ 78 | { text: 'Getting Started', link: '/guide/' }, 79 | { text: 'Comparisons', link: '/guide/comparisons' }, 80 | { text: 'Migration Guide', link: '/guide/migration' }, 81 | ], 82 | }, 83 | { 84 | text: 'Animations', 85 | items: [ 86 | { text: 'Base', link: '/animations/' }, 87 | { text: 'Fade', link: '/animations/fade' }, 88 | { text: 'Zoom', link: '/animations/zoom' }, 89 | { text: 'Spin', link: '/animations/spin' }, 90 | { text: 'Slide', link: '/animations/slide' }, 91 | { text: 'Animation Properties', link: '/animations/animation-properties' }, 92 | ], 93 | }, 94 | { text: 'Interactive Documentation', link: '/interactive-documentation' }, 95 | ], 96 | 97 | socialLinks: [ 98 | { icon: 'github', link: 'https://github.com/xsjcTony/unocss-preset-animations' }, 99 | ], 100 | }, 101 | }) 102 | -------------------------------------------------------------------------------- /docs/src/animations/fade.md: -------------------------------------------------------------------------------- 1 | # Fade Animations 2 | 3 | Use classname `fade-(in|out)-` to define animation's `opacity`. 4 | 5 | - The `-` part is ***optional***. 6 | 7 |
8 | 9 | You can use either of below as the ``: 10 | 11 | - Any number between `0` and `100` including decimals. E.g. `fade-in-50` 12 | - Any percentage between `0%` and `100%` including decimals, E.g. `fade-in-66.6%` 13 | - CSS variable. E.g. `fade-in-$my-css-var` 14 | 15 | ## Fade In 16 | 17 | Define enter animation's starting `opacity`. 18 | 19 | - The default **value** is `0` if not specified. 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
ClassnameProperty
fade-in--una-enter-opacity: 0;
fade-in-0
fade-in-0%
fade-in-10--una-enter-opacity: 0.1;
fade-in-10%
fade-in-.8--una-enter-opacity: 0.008;
fade-in-0.8
fade-in-.8%
fade-in-52.1--una-enter-opacity: 0.521;
fade-in-66.66--una-enter-opacity: 0.6666;
fade-in-100--una-enter-opacity: 1;
fade-in-$my-css-var--una-enter-opacity: var(--my-css-var);
81 | 82 | ## Fade Out 83 | 84 | Define exit animation's ending `opacity`. 85 | 86 | - The default **value** is `0` if not specified. 87 | 88 | ```html 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
ClassnameProperty
fade-out--una-exit-opacity: 0;
fade-out-0
fade-out-0%
fade-out-10--una-exit-opacity: 0.1;
fade-out-10%
fade-out-.8--una-exit-opacity: 0.008;
fade-out-0.8
fade-out-.8%
fade-out-52.1--una-exit-opacity: 0.521;
fade-out-66.66--una-exit-opacity: 0.6666;
fade-out-100--una-exit-opacity: 1;
fade-out-$my-css-var--una-exit-opacity: var(--my-css-var);
148 | -------------------------------------------------------------------------------- /docs/src/animations/zoom.md: -------------------------------------------------------------------------------- 1 | # Zoom Animations 2 | 3 | Use classname `zoom-(in|out)-` to define animation's `scale`. 4 | 5 | - The `-` part is ***optional***. 6 | 7 |
8 | 9 | You can use either of below as the ``: 10 | 11 | - Any number including negative and decimals. E.g. `zoom-in-50` 12 | - Any percentage including negative and decimals. E.g. `zoom-in-66.6%` 13 | - Any fraction including negative. E.g. `zoom-in-1/2` 14 | - `full` as `100%`. E.g. `zoom-in-full` 15 | - CSS variable. E.g. `zoom-in-$my-css-var` 16 | 17 | ## Zoom In 18 | 19 | Define enter animation's starting `scale`. 20 | 21 | - The default **value** is `0` if not specified. 22 | 23 | ```html 24 | 25 | 26 | 27 | 28 | 29 | 30 | ``` 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
ClassnameProperty
zoom-in--una-enter-scale: 0;
zoom-in-0
zoom-in-0%
zoom-in-10--una-enter-scale: 0.1;
zoom-in-10%
zoom-in-.8--una-enter-scale: 0.008;
zoom-in-0.8
zoom-in-.8%
zoom-in-52.1--una-enter-scale: 0.521;
zoom-in-100--una-enter-scale: 1;
zoom-in--20--una-enter-scale: -0.2;
zoom-in--20%
zoom-in--66.66--una-enter-scale: -0.6666;
zoom-in-1/3--una-enter-scale: 33.3333333333%;
zoom-in--2/3--una-enter-scale: -66.6666666667%;
zoom-in-full--una-enter-scale: 100%;
zoom-in-$my-css-var--una-enter-scale: var(--my-css-var);
104 | 105 | ## Zoom Out 106 | 107 | Define exit animation's ending `scale`. 108 | 109 | - The default **value** is `0` if not specified. 110 | 111 | ```html 112 | 113 | 114 | 115 | 116 | 117 | 118 | ``` 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
ClassnameProperty
zoom-out--una-exit-scale: 0;
zoom-out-0
zoom-out-0%
zoom-out-10--una-exit-scale: 0.1;
zoom-out-10%
zoom-out-.8--una-exit-scale: 0.008;
zoom-out-0.8
zoom-out-.8%
zoom-out-52.1--una-exit-scale: 0.521;
zoom-out-100--una-exit-scale: 1;
zoom-out--20--una-exit-scale: -0.2;
zoom-out--20%
zoom-out--66.66--una-exit-scale: -0.6666;
zoom-out-1/3--una-exit-scale: 33.3333333333%;
zoom-out--2/3--una-exit-scale: -66.6666666667%;
zoom-out-full--una-exit-scale: 100%;
zoom-out-$my-css-var--una-exit-scale: var(--my-css-var);
192 | -------------------------------------------------------------------------------- /docs/src/guide/migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: [2, 3] 3 | next: Animations 4 | --- 5 | 6 | # Migration Guide 7 | 8 | ## tailwindcss-animate 9 | 10 | Animations part remains as is, but way more flexible and powerful thanks to UnoCSS's [dynamic rules](https://unocss.dev/config/rules#dynamic-rules). Please refer to [Animations](/animations/). 11 | 12 | Animation property modifiers are already supported by `@unocss/preset-wind3`, but with slightly different syntax. 13 | 14 | ### `animation-delay` 15 | 16 | | tailwindcss-animate | unocss-preset-animations | 17 | |---------------------|--------------------------| 18 | | `delay-` | `animate-delay-` | 19 | 20 | ```html 21 | 22 | 23 | 24 | ``` 25 | 26 | ### `animation-direction` 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
tailwindcss-animateunocss-preset-animations
direction-<keyword>animate-<keyword>
animate-direction-<keyword>
41 | 42 | ```html 43 | 44 | 45 | 46 | 47 | 48 | 49 | ``` 50 | 51 | ### `animation-duration` 52 | 53 | | tailwindcss-animate | unocss-preset-animations | 54 | |---------------------|-----------------------------| 55 | | `duration-` | `animate-duration-` | 56 | 57 | ```html 58 | 59 | 60 | 61 | ``` 62 | 63 | ::: tip 64 | If no `animation-duration` is defined, it will fall back to `theme.duration.DEFAULT`. 65 | 66 | The value is `150ms` by default if unchanged, see [unocss/packages/preset-mini/src/_theme/misc.ts at main · unocss/unocss](https://github.com/unocss/unocss/blob/efdc358897a308323e1d943dd0f7c13e344e1495/packages/preset-mini/src/_theme/misc.ts#L37) 67 | ::: 68 | 69 | ### `animation-fill-mode` 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
tailwindcss-animateunocss-preset-animations
fill-mode-<keyword>animate-<keyword>
animate-fill-<keyword>
animate-mode-<keyword>
animate-fill-mode-<keyword>
90 | 91 | ```html 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ``` 101 | 102 | ### `animation-iteration-count` 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
tailwindcss-animateunocss-preset-animations
repeat-<number|keyword>animate-iteration-<number|keyword>
animate-count-<number|keyword>
animate-iteration-count-<number|keyword>
120 | 121 | ```html 122 | 123 | 124 | 125 | 126 | 127 | 128 | ``` 129 | 130 | ### `animation-play-state` 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 |
tailwindcss-animateunocss-preset-animations
running / pausedanimate-<keyword>
animate-play-<keyword>
animate-state-<keyword>
animate-play-state-<keyword>
151 | 152 | ```html 153 | 154 | 155 | 156 | 157 | 158 | 159 | ``` 160 | 161 | ### `animation-timing-function` 162 | 163 | | tailwindcss-animate | unocss-preset-animations | 164 | |---------------------|--------------------------| 165 | | `ease-` | `animate-ease-` | 166 | 167 | ```html 168 | 169 | 170 | 171 | 172 | ``` 173 | 174 | ### Prefers reduced motion 175 | 176 | Usage is the same as in TailwindCSS. 177 | 178 | ```html 179 | 180 | 181 | ``` 182 | -------------------------------------------------------------------------------- /test/base.test.ts: -------------------------------------------------------------------------------- 1 | import type { PresetAnimationsOptions } from '@/index' 2 | import { describe, it } from 'vitest' 3 | import { generator, uno } from '~/utils' 4 | 5 | 6 | describe.concurrent('base', () => { 7 | describe('animate classnames', () => { 8 | it('"animate-in" should generate enter keyframe and css variables', async ({ expect }) => { 9 | const { css } = await uno.generate('animate-in') 10 | 11 | expect(css).toMatchInlineSnapshot(` 12 | "/* layer: una-base */ 13 | .animate-in{animation:una-in;animation-name:una-in;animation-duration:150ms;--una-enter-opacity:initial;--una-enter-scale:initial;--una-enter-rotate:initial;--una-enter-translate-x:initial;--una-enter-translate-y:initial;} 14 | /* layer: default */ 15 | @keyframes una-in{from{opacity:var(--una-enter-opacity,1);transform:translate3d(var(--una-enter-translate-x,0),var(--una-enter-translate-y,0),0) scale3d(var(--una-enter-scale,1),var(--una-enter-scale,1),var(--una-enter-scale,1)) rotate(var(--una-enter-rotate,0))}}" 16 | `) 17 | }) 18 | 19 | 20 | it('"animate-out" should generate exit keyframe and css variables', async ({ expect }) => { 21 | const { css } = await uno.generate('animate-out') 22 | 23 | expect(css).toMatchInlineSnapshot(` 24 | "/* layer: una-base */ 25 | .animate-out{animation:una-out;animation-name:una-out;animation-duration:150ms;--una-exit-opacity:initial;--una-exit-scale:initial;--una-exit-rotate:initial;--una-exit-translate-x:initial;--una-exit-translate-y:initial;} 26 | /* layer: default */ 27 | @keyframes una-out{to{opacity:var(--una-exit-opacity,1);transform:translate3d(var(--una-exit-translate-x,0),var(--una-exit-translate-y,0),0) scale3d(var(--una-exit-scale,1),var(--una-exit-scale,1),var(--una-exit-scale,1)) rotate(var(--una-exit-rotate,0))}}" 28 | `) 29 | }) 30 | }) 31 | 32 | 33 | it('"animation-duration" should be default to "theme.duration"', async ({ expect }) => { 34 | const DURATION = '500ms' 35 | 36 | const uno = await generator({ theme: { duration: { DEFAULT: DURATION } } }) 37 | const { css } = await uno.generate('animate-in') 38 | 39 | expect(css).toContain(`animation-duration:${DURATION};`) 40 | }) 41 | 42 | 43 | describe('options', () => { 44 | const DEFAULT_UNIT = 'ms' 45 | 46 | 47 | describe('unit', () => { 48 | it('should respect the given value', async ({ expect }) => { 49 | const DURATION = 500 50 | const UNIT: NonNullable = 's' 51 | 52 | const uno = await generator({ presetOptions: { unit: UNIT, duration: DURATION } }) 53 | const { css } = await uno.generate('animate-in') 54 | 55 | expect(css).toContain(`animation-duration:${DURATION}${UNIT}`) 56 | }) 57 | 58 | 59 | it('should default to "ms" if no unit is provided', async ({ expect }) => { 60 | const DURATION = 500 61 | 62 | const uno = await generator({ presetOptions: { duration: DURATION } }) 63 | const { css } = await uno.generate('animate-in') 64 | 65 | expect(css).toContain(`animation-duration:${DURATION}${DEFAULT_UNIT}`) 66 | }) 67 | }) 68 | 69 | 70 | describe('delay', () => { 71 | it('should generate "animation-delay" based on given value', async ({ expect }) => { 72 | const DELAY = 100 73 | 74 | const uno = await generator({ presetOptions: { delay: DELAY } }) 75 | const { css } = await uno.generate('animate-in') 76 | 77 | expect(css).toContain(`animation-delay:${DELAY}${DEFAULT_UNIT}`) 78 | }) 79 | 80 | 81 | it('should not generate "animation-delay" if no value is provided', async ({ expect }) => { 82 | const { css } = await uno.generate('animate-in') 83 | 84 | expect(css).not.toContain('animation-delay') 85 | }) 86 | }) 87 | 88 | 89 | describe('direction', () => { 90 | it('should generate "animation-direction" based on given value', async ({ expect }) => { 91 | const DIRECTION: NonNullable = 'reverse' 92 | 93 | const uno = await generator({ presetOptions: { direction: DIRECTION } }) 94 | const { css } = await uno.generate('animate-in') 95 | 96 | expect(css).toContain(`animation-direction:${DIRECTION}`) 97 | }) 98 | 99 | 100 | it('should not generate "animation-direction" if no value is provided', async ({ expect }) => { 101 | const { css } = await uno.generate('animate-in') 102 | 103 | expect(css).not.toContain('animation-direction') 104 | }) 105 | }) 106 | 107 | 108 | describe('duration', () => { 109 | it('should generate "animation-duration" based on given value', async ({ expect }) => { 110 | const DURATION = 100 111 | 112 | const uno = await generator({ presetOptions: { duration: DURATION } }) 113 | const { css } = await uno.generate('animate-in') 114 | 115 | expect(css).toContain(`animation-duration:${DURATION}${DEFAULT_UNIT}`) 116 | }) 117 | 118 | 119 | it('should fallback to default "animation-duration" if no value is provided', async ({ expect }) => { 120 | const { css } = await uno.generate('animate-in') 121 | 122 | expect(css).toContain('animation-duration') 123 | }) 124 | }) 125 | 126 | 127 | describe('fill-mode', () => { 128 | it('should generate "animation-fill-mode" based on given value', async ({ expect }) => { 129 | const FILL_MODE: NonNullable = 'forwards' 130 | 131 | const uno = await generator({ presetOptions: { fillMode: FILL_MODE } }) 132 | const { css } = await uno.generate('animate-in') 133 | 134 | expect(css).toContain(`animation-fill-mode:${FILL_MODE}`) 135 | }) 136 | 137 | 138 | it('should not generate "animation-fill-mode" if no value is provided', async ({ expect }) => { 139 | const { css } = await uno.generate('animate-in') 140 | 141 | expect(css).not.toContain('animation-fill-mode') 142 | }) 143 | }) 144 | 145 | 146 | describe('iteration-count', () => { 147 | it('should generate "animation-iteration-count" based on given value', async ({ expect }) => { 148 | const ITERATION_COUNT = 3 149 | 150 | const uno = await generator({ presetOptions: { iterationCount: ITERATION_COUNT } }) 151 | const { css } = await uno.generate('animate-in') 152 | 153 | expect(css).toContain(`animation-iteration-count:${ITERATION_COUNT}`) 154 | }) 155 | 156 | 157 | it('should not generate "animation-iteration-count" if no value is provided', async ({ expect }) => { 158 | const { css } = await uno.generate('animate-in') 159 | 160 | expect(css).not.toContain('animation-iteration-count') 161 | }) 162 | }) 163 | 164 | 165 | describe('play-state', () => { 166 | it('should generate "animation-play-state" based on given value', async ({ expect }) => { 167 | const PLAY_STATE: NonNullable = 'paused' 168 | 169 | const uno = await generator({ presetOptions: { playState: PLAY_STATE } }) 170 | const { css } = await uno.generate('animate-in') 171 | 172 | expect(css).toContain(`animation-play-state:${PLAY_STATE}`) 173 | }) 174 | 175 | 176 | it('should not generate "animation-play-state" if no value is provided', async ({ expect }) => { 177 | const { css } = await uno.generate('animate-in') 178 | 179 | expect(css).not.toContain('animation-play-state') 180 | }) 181 | }) 182 | 183 | 184 | describe('timing-function', () => { 185 | it('should generate "animation-timing-function" based on given value', async ({ expect }) => { 186 | const TIMING_FUNCTION: NonNullable = 'ease-in-out' 187 | 188 | const uno = await generator({ presetOptions: { timingFunction: TIMING_FUNCTION } }) 189 | const { css } = await uno.generate('animate-in') 190 | 191 | expect(css).toContain(`animation-timing-function:${TIMING_FUNCTION}`) 192 | }) 193 | 194 | 195 | it('should not generate "animation-timing-function" if no value is provided', async ({ expect }) => { 196 | const { css } = await uno.generate('animate-in') 197 | 198 | expect(css).not.toContain('animation-timing-function') 199 | }) 200 | }) 201 | }) 202 | }) 203 | -------------------------------------------------------------------------------- /test/fade.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { CSS_VARIABLE_PREFIX } from '@/constants' 3 | import { CSS_VARIABLES, DECIMALS_0_TO_100, INTEGERS_0_TO_100 } from '~/data' 4 | import { uno } from '~/utils' 5 | 6 | 7 | describe.concurrent('fade animation', () => { 8 | describe('fade-in', () => { 9 | it(`should generate "${CSS_VARIABLE_PREFIX}-enter-opacity" css variable and default to "0"`, async ({ expect }) => { 10 | const { css } = await uno.generate('fade-in') 11 | 12 | expect(css).toContain(`.fade-in{${CSS_VARIABLE_PREFIX}-enter-opacity:0;}`) 13 | }) 14 | 15 | 16 | describe('percentage', () => { 17 | it(`should convert percentages from "0" to "100"`, async ({ expect }) => { 18 | const classnames = INTEGERS_0_TO_100.map(i => `fade-in-${i}`) 19 | 20 | const { matched, css } = await uno.generate(classnames.join(' ')) 21 | 22 | expect(matched).toStrictEqual(new Set(classnames)) 23 | expect(css).toMatchInlineSnapshot(` 24 | "/* layer: default */ 25 | .fade-in-0{--una-enter-opacity:0;} 26 | .fade-in-10{--una-enter-opacity:0.1;} 27 | .fade-in-100{--una-enter-opacity:1;} 28 | .fade-in-20{--una-enter-opacity:0.2;} 29 | .fade-in-30{--una-enter-opacity:0.3;} 30 | .fade-in-40{--una-enter-opacity:0.4;} 31 | .fade-in-50{--una-enter-opacity:0.5;} 32 | .fade-in-60{--una-enter-opacity:0.6;} 33 | .fade-in-70{--una-enter-opacity:0.7;} 34 | .fade-in-80{--una-enter-opacity:0.8;} 35 | .fade-in-90{--una-enter-opacity:0.9;}" 36 | `) 37 | }) 38 | 39 | 40 | it(`should also convert decimals`, async ({ expect }) => { 41 | const classnames = DECIMALS_0_TO_100.map(i => `fade-in-${i}`) 42 | 43 | const { matched, css } = await uno.generate(classnames.join(' ')) 44 | 45 | expect(matched).toStrictEqual(new Set(classnames)) 46 | expect(css).toMatchInlineSnapshot(` 47 | "/* layer: default */ 48 | .fade-in-0\\.1{--una-enter-opacity:0.001;} 49 | .fade-in-10\\.1{--una-enter-opacity:0.101;} 50 | .fade-in-52\\.1{--una-enter-opacity:0.521;} 51 | .fade-in-66\\.66{--una-enter-opacity:0.6666;} 52 | .fade-in-99\\.9{--una-enter-opacity:0.999;}" 53 | `) 54 | }) 55 | 56 | 57 | it(`should also convert both integers and decimals with "%" symbol`, async ({ expect }) => { 58 | const classnames = [ 59 | ...INTEGERS_0_TO_100.map(i => `fade-in-${i}%`), 60 | ...DECIMALS_0_TO_100.map(i => `fade-in-${i}%`), 61 | ] 62 | 63 | const { matched, css } = await uno.generate(classnames.join(' ')) 64 | 65 | expect(matched).toStrictEqual(new Set(classnames)) 66 | expect(css).toMatchInlineSnapshot(` 67 | "/* layer: default */ 68 | .fade-in-0\\.1\\%{--una-enter-opacity:0.001;} 69 | .fade-in-0\\%{--una-enter-opacity:0;} 70 | .fade-in-10\\.1\\%{--una-enter-opacity:0.101;} 71 | .fade-in-10\\%{--una-enter-opacity:0.1;} 72 | .fade-in-100\\%{--una-enter-opacity:1;} 73 | .fade-in-20\\%{--una-enter-opacity:0.2;} 74 | .fade-in-30\\%{--una-enter-opacity:0.3;} 75 | .fade-in-40\\%{--una-enter-opacity:0.4;} 76 | .fade-in-50\\%{--una-enter-opacity:0.5;} 77 | .fade-in-52\\.1\\%{--una-enter-opacity:0.521;} 78 | .fade-in-60\\%{--una-enter-opacity:0.6;} 79 | .fade-in-66\\.66\\%{--una-enter-opacity:0.6666;} 80 | .fade-in-70\\%{--una-enter-opacity:0.7;} 81 | .fade-in-80\\%{--una-enter-opacity:0.8;} 82 | .fade-in-90\\%{--una-enter-opacity:0.9;} 83 | .fade-in-99\\.9\\%{--una-enter-opacity:0.999;}" 84 | `) 85 | }) 86 | }) 87 | 88 | 89 | describe('css variable', () => { 90 | it(`should handle css variables`, async ({ expect }) => { 91 | const classnames = CSS_VARIABLES.map(i => `fade-in-${i}`) 92 | 93 | const { matched, css } = await uno.generate(classnames.join(' ')) 94 | 95 | expect(matched).toStrictEqual(new Set(classnames)) 96 | expect(css).toMatchInlineSnapshot(` 97 | "/* layer: default */ 98 | .fade-in-\\$foo{--una-enter-opacity:var(--foo);} 99 | .fade-in-\\$foo-bar{--una-enter-opacity:var(--foo-bar);} 100 | .fade-in-\\$fooBar{--una-enter-opacity:var(--fooBar);}" 101 | `) 102 | }) 103 | }) 104 | }) 105 | 106 | 107 | describe('fade-out', () => { 108 | it(`should generate "${CSS_VARIABLE_PREFIX}-exit-opacity" css variable and default to "0"`, async ({ expect }) => { 109 | const { css } = await uno.generate('fade-out') 110 | 111 | expect(css).toContain(`.fade-out{${CSS_VARIABLE_PREFIX}-exit-opacity:0;}`) 112 | }) 113 | 114 | 115 | describe('percentage', () => { 116 | it(`should convert percentages from "0" to "100"`, async ({ expect }) => { 117 | const classnames = INTEGERS_0_TO_100.map(i => `fade-out-${i}`) 118 | 119 | const { matched, css } = await uno.generate(classnames.join(' ')) 120 | 121 | expect(matched).toStrictEqual(new Set(classnames)) 122 | expect(css).toMatchInlineSnapshot(` 123 | "/* layer: default */ 124 | .fade-out-0{--una-exit-opacity:0;} 125 | .fade-out-10{--una-exit-opacity:0.1;} 126 | .fade-out-100{--una-exit-opacity:1;} 127 | .fade-out-20{--una-exit-opacity:0.2;} 128 | .fade-out-30{--una-exit-opacity:0.3;} 129 | .fade-out-40{--una-exit-opacity:0.4;} 130 | .fade-out-50{--una-exit-opacity:0.5;} 131 | .fade-out-60{--una-exit-opacity:0.6;} 132 | .fade-out-70{--una-exit-opacity:0.7;} 133 | .fade-out-80{--una-exit-opacity:0.8;} 134 | .fade-out-90{--una-exit-opacity:0.9;}" 135 | `) 136 | }) 137 | 138 | 139 | it(`should also convert decimals`, async ({ expect }) => { 140 | const classnames = DECIMALS_0_TO_100.map(i => `fade-out-${i}`) 141 | 142 | const { matched, css } = await uno.generate(classnames.join(' ')) 143 | 144 | expect(matched).toStrictEqual(new Set(classnames)) 145 | expect(css).toMatchInlineSnapshot(` 146 | "/* layer: default */ 147 | .fade-out-0\\.1{--una-exit-opacity:0.001;} 148 | .fade-out-10\\.1{--una-exit-opacity:0.101;} 149 | .fade-out-52\\.1{--una-exit-opacity:0.521;} 150 | .fade-out-66\\.66{--una-exit-opacity:0.6666;} 151 | .fade-out-99\\.9{--una-exit-opacity:0.999;}" 152 | `) 153 | }) 154 | 155 | 156 | it(`should also convert both integers and decimals with "%" symbol`, async ({ expect }) => { 157 | const classnames = [ 158 | ...INTEGERS_0_TO_100.map(i => `fade-out-${i}%`), 159 | ...DECIMALS_0_TO_100.map(i => `fade-out-${i}%`), 160 | ] 161 | 162 | const { matched, css } = await uno.generate(classnames.join(' ')) 163 | 164 | expect(matched).toStrictEqual(new Set(classnames)) 165 | expect(css).toMatchInlineSnapshot(` 166 | "/* layer: default */ 167 | .fade-out-0\\.1\\%{--una-exit-opacity:0.001;} 168 | .fade-out-0\\%{--una-exit-opacity:0;} 169 | .fade-out-10\\.1\\%{--una-exit-opacity:0.101;} 170 | .fade-out-10\\%{--una-exit-opacity:0.1;} 171 | .fade-out-100\\%{--una-exit-opacity:1;} 172 | .fade-out-20\\%{--una-exit-opacity:0.2;} 173 | .fade-out-30\\%{--una-exit-opacity:0.3;} 174 | .fade-out-40\\%{--una-exit-opacity:0.4;} 175 | .fade-out-50\\%{--una-exit-opacity:0.5;} 176 | .fade-out-52\\.1\\%{--una-exit-opacity:0.521;} 177 | .fade-out-60\\%{--una-exit-opacity:0.6;} 178 | .fade-out-66\\.66\\%{--una-exit-opacity:0.6666;} 179 | .fade-out-70\\%{--una-exit-opacity:0.7;} 180 | .fade-out-80\\%{--una-exit-opacity:0.8;} 181 | .fade-out-90\\%{--una-exit-opacity:0.9;} 182 | .fade-out-99\\.9\\%{--una-exit-opacity:0.999;}" 183 | `) 184 | }) 185 | }) 186 | 187 | 188 | describe('css variable', () => { 189 | it(`should handle css variables`, async ({ expect }) => { 190 | const classnames = CSS_VARIABLES.map(i => `fade-out-${i}`) 191 | 192 | const { matched, css } = await uno.generate(classnames.join(' ')) 193 | 194 | expect(matched).toStrictEqual(new Set(classnames)) 195 | expect(css).toMatchInlineSnapshot(` 196 | "/* layer: default */ 197 | .fade-out-\\$foo{--una-exit-opacity:var(--foo);} 198 | .fade-out-\\$foo-bar{--una-exit-opacity:var(--foo-bar);} 199 | .fade-out-\\$fooBar{--una-exit-opacity:var(--fooBar);}" 200 | `) 201 | }) 202 | }) 203 | }) 204 | }) 205 | -------------------------------------------------------------------------------- /docs/src/animations/slide.md: -------------------------------------------------------------------------------- 1 | # Slide Animations 2 | 3 | Use classname `slide-(in|out)--` to define animation's `translate`. 4 | 5 | - The `-` part is ***optional***. 6 | - You can optionally use `slide-in-from` / `slide-out-to` instead of `slide-in` / `slide-out` to make it more readable 7 | 8 |
9 | 10 | You can use either of below as the `` (alias / full): 11 | 12 | - `t` / `top` 13 | - `b` / `bottom` 14 | - `l` / `left` 15 | - `r` / `right` 16 | 17 | ::: tip 18 | `top` / `bottom` and `left` / `right` can be used together. 19 | 20 | | y-axis Direction | x-axis Direction | Result | 21 | |------------------|------------------|--------| 22 | | `t` / `top` | `l` / `left` | ↖️ | 23 | | `t` / `top` | `r` / `right` | ↗️ | 24 | | `b` / `bottom` | `l` / `left` | ↙️ | 25 | | `b` / `bottom` | `r` / `right` | ↘️ | 26 | 27 | E.g. the following combination will make the element slide in from `top-left` corner. [Playground](https://unocss.dev/play/?html=DwEwlgbgBAxgNgQwM5ILwCIAWBaJMBOApoQHZQDm%2BYIUADojIdmAC6EC2S2jJb%2B6APgBQUKMABGAVxYsA9mXjI06BCTDsEbZmSRxqTMCWwtsARgD0AJii7927HGwAWKADMEIA0YAMUVes0mT0QATzNvCL81DS0QSXxNMHlwyPFZfE98OmxrfFlJEk8QB3JBEVEoACFpOTIAQXLgcykZeWEm8AhhIA&config=JYWwDg9gTgLgBAbwFBzgEwKYDNgDsMDCEuOA5gDQpxhQYDOGMAgjDFMAEYCuMwWAnpVQ16jAJIBjYnSHVaDGAFVcESgF84WKBBBwA5FxUS6dPUlCRYiOaOa5QAQ17S4GrTv2GIxugFoRCr4O9iBOwNJmSEgYAB6W8JhYDlwANgnYeITEZAAUyMLyjHQAXHAA2lQFtsoQOQCUslUKTCFh0nmawCkpALIQmKV6HBAwABZ6rg1UALrqdUhAA&css=PQKgBA6gTglgLgUzAYwK4Gc4HsC2YDCAyoWABYJQIA0YAhgHYAmYcUD6AZllDhWOqgAOg7nAB0YAGLcwCAB60cggDYIAXGBDAAUKDBi0mXGADe2sGC704AWgDuCGAHNScDQFYADJ4Dc5sAACtMLKAJ5gggCMLPK2ABR2pPBIcsoAlH4WAEa0yADWTlBYqEw2yFjK3Bpw5LxxAOTllVDoYpSMYgAs3vUZ2gC%2BmsBAA&options=N4IgLgTghgdgzgMwPYQLYAkyoDYgFwJTZwCmAvkA) 28 | 29 | ```html 30 | 31 | ``` 32 | ::: 33 | 34 |
35 | 36 | You can use either of below as the ``: 37 | 38 | - Any number including negative(⚠️) and decimals. E.g. `slide-in-t-50` 39 | - Any percentage including negative(⚠️) and decimals. E.g. `slide-in-t-66.6%` 40 | - Any fraction including negative(⚠️). E.g. `slide-in-t-1/2` 41 | - `full` as `100%`. E.g. `slide-in-t-full` 42 | - CSS variable. E.g. `slide-in-t-$my-css-var` 43 | 44 | ::: warning 45 | Using ***NEGATIVE*** value is discouraged. Although it will work as expected, but can be misleading or hard to understand. 46 | 47 | You can always switch to the opposite direction to avoid using negative values. 48 | 49 | E.g. 50 | ```html 51 | 52 | 53 | ``` 54 | is equivalent to 55 | ```html 56 | 57 | 58 | ``` 59 | Note: `CSS variable` usages will be placed as is **WITHOUT** any direction or positive / negative check. 60 | ::: 61 | 62 | ## Slide In 63 | 64 | Define enter animation's starting `translate`. 65 | 66 | - The default **value** is `100%` if not specified. 67 | 68 | ```html 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ``` 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 |
ClassnameProperty
slide-in-t--una-enter-translate-y: -100%;
slide-in-top
slide-in-from-t
slide-in-from-top
slide-in-t-full
slide-in-b--una-enter-translate-y: 100%;
slide-in-l--una-enter-translate-x: -100%;
slide-in-r--una-enter-translate-x: 100%;
slide-in-b-10--una-enter-translate-y: 2.5rem;
slide-in-b-48%--una-enter-translate-y: 48%;
slide-in-b-.8--una-enter-translate-y: 0.2rem;
slide-in-b-52.1--una-enter-translate-y: 13.025rem;
slide-in-b-66.6%--una-enter-translate-y: 66.6%;
slide-in-b-100--una-enter-translate-y: 25rem;
slide-in-b--20 ⚠️--una-enter-translate-y: -5rem;
slide-in-b--48% ⚠️--una-enter-translate-y: -48%;
slide-in-b--66.66 ⚠️--una-enter-translate-y: -16.665rem;
slide-in-t--20 ⚠️--una-enter-translate-y: 5rem;
slide-in-t--48% ⚠️--una-enter-translate-y: 48%;
slide-in-t--66.66 ⚠️--una-enter-translate-y: 16.665rem;
slide-in-b-1/3--una-enter-translate-y: 33.3333333333%;
slide-in-b--2/3 ⚠️--una-enter-translate-y: -66.6666666667%;
slide-in-t--2/3 ⚠️--una-enter-translate-y: 66.6666666667%;
slide-in-b-full--una-enter-translate-y: 100%;
slide-in-t-$my-css-var--una-enter-translate-y: var(--my-css-var);
slide-in-b-$my-css-var
slide-in-l-$my-css-var--una-enter-translate-x: var(--my-css-var);
slide-in-r-$my-css-var
196 | 197 | ## Slide Out 198 | 199 | Define exit animation's starting `translate`. 200 | 201 | - The default **value** is `100%` if not specified. 202 | 203 | ```html 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | ``` 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 |
ClassnameProperty
slide-out-t--una-exit-translate-y: -100%;
slide-out-top
slide-out-to-t
slide-out-to-top
slide-out-t-full
slide-out-b--una-exit-translate-y: 100%;
slide-out-l--una-exit-translate-x: -100%;
slide-out-r--una-exit-translate-x: 100%;
slide-out-b-10--una-exit-translate-y: 2.5rem;
slide-out-b-48%--una-exit-translate-y: 48%;
slide-out-b-.8--una-exit-translate-y: 0.2rem;
slide-out-b-52.1--una-exit-translate-y: 13.025rem;
slide-out-b-66.6%--una-exit-translate-y: 66.6%;
slide-out-b-100--una-exit-translate-y: 25rem;
slide-out-b--20 ⚠️--una-exit-translate-y: -5rem;
slide-out-b--48% ⚠️--una-exit-translate-y: -48%;
slide-out-b--66.66 ⚠️--una-exit-translate-y: -16.665rem;
slide-out-t--20 ⚠️--una-exit-translate-y: 5rem;
slide-out-t--48% ⚠️--una-exit-translate-y: 48%;
slide-out-t--66.66 ⚠️--una-exit-translate-y: 16.665rem;
slide-out-b-1/3--una-exit-translate-y: 33.3333333333%;
slide-out-b--2/3 ⚠️--una-exit-translate-y: -66.6666666667%;
slide-out-t--2/3 ⚠️--una-exit-translate-y: 66.6666666667%;
slide-out-b-full--una-exit-translate-y: 100%;
slide-out-t-$my-css-var--una-exit-translate-y: var(--my-css-var);
slide-out-b-$my-css-var
slide-out-l-$my-css-var--una-exit-translate-x: var(--my-css-var);
slide-out-r-$my-css-var
331 | -------------------------------------------------------------------------------- /test/zoom.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { CSS_VARIABLE_PREFIX } from '@/constants' 3 | import { CSS_VARIABLES, DECIMALS, FRACTIONS, INTEGERS } from '~/data' 4 | import { uno } from '~/utils' 5 | 6 | 7 | describe.concurrent('zoom animation', () => { 8 | describe('zoom-in', () => { 9 | it(`should generate "${CSS_VARIABLE_PREFIX}-enter-scale" css variable and default to "0"`, async ({ expect }) => { 10 | const { css } = await uno.generate('zoom-in') 11 | 12 | expect(css).toContain(`.zoom-in{${CSS_VARIABLE_PREFIX}-enter-scale:0;}`) 13 | }) 14 | 15 | 16 | describe('percentage', () => { 17 | it(`should convert any percentages including negative`, async ({ expect }) => { 18 | const classnames = INTEGERS.map(i => `zoom-in-${i}`) 19 | 20 | const { matched, css } = await uno.generate(classnames.join(' ')) 21 | 22 | expect(matched).toStrictEqual(new Set(classnames)) 23 | expect(css).toMatchInlineSnapshot(` 24 | "/* layer: default */ 25 | .zoom-in--10{--una-enter-scale:-0.1;} 26 | .zoom-in--100{--una-enter-scale:-1;} 27 | .zoom-in--110{--una-enter-scale:-1.1;} 28 | .zoom-in--120{--una-enter-scale:-1.2;} 29 | .zoom-in--130{--una-enter-scale:-1.3;} 30 | .zoom-in--140{--una-enter-scale:-1.4;} 31 | .zoom-in--150{--una-enter-scale:-1.5;} 32 | .zoom-in--160{--una-enter-scale:-1.6;} 33 | .zoom-in--170{--una-enter-scale:-1.7;} 34 | .zoom-in--180{--una-enter-scale:-1.8;} 35 | .zoom-in--190{--una-enter-scale:-1.9;} 36 | .zoom-in--20{--una-enter-scale:-0.2;} 37 | .zoom-in--200{--una-enter-scale:-2;} 38 | .zoom-in--30{--una-enter-scale:-0.3;} 39 | .zoom-in--40{--una-enter-scale:-0.4;} 40 | .zoom-in--50{--una-enter-scale:-0.5;} 41 | .zoom-in--60{--una-enter-scale:-0.6;} 42 | .zoom-in--70{--una-enter-scale:-0.7;} 43 | .zoom-in--80{--una-enter-scale:-0.8;} 44 | .zoom-in--90{--una-enter-scale:-0.9;} 45 | .zoom-in-0{--una-enter-scale:0;} 46 | .zoom-in-10{--una-enter-scale:0.1;} 47 | .zoom-in-100{--una-enter-scale:1;} 48 | .zoom-in-110{--una-enter-scale:1.1;} 49 | .zoom-in-120{--una-enter-scale:1.2;} 50 | .zoom-in-130{--una-enter-scale:1.3;} 51 | .zoom-in-140{--una-enter-scale:1.4;} 52 | .zoom-in-150{--una-enter-scale:1.5;} 53 | .zoom-in-160{--una-enter-scale:1.6;} 54 | .zoom-in-170{--una-enter-scale:1.7;} 55 | .zoom-in-180{--una-enter-scale:1.8;} 56 | .zoom-in-190{--una-enter-scale:1.9;} 57 | .zoom-in-20{--una-enter-scale:0.2;} 58 | .zoom-in-200{--una-enter-scale:2;} 59 | .zoom-in-30{--una-enter-scale:0.3;} 60 | .zoom-in-40{--una-enter-scale:0.4;} 61 | .zoom-in-50{--una-enter-scale:0.5;} 62 | .zoom-in-60{--una-enter-scale:0.6;} 63 | .zoom-in-70{--una-enter-scale:0.7;} 64 | .zoom-in-80{--una-enter-scale:0.8;} 65 | .zoom-in-90{--una-enter-scale:0.9;}" 66 | `) 67 | }) 68 | 69 | 70 | it(`should also convert decimals including negative`, async ({ expect }) => { 71 | const classnames = DECIMALS.map(i => `zoom-in-${i}`) 72 | 73 | const { matched, css } = await uno.generate(classnames.join(' ')) 74 | 75 | expect(matched).toStrictEqual(new Set(classnames)) 76 | expect(css).toMatchInlineSnapshot(` 77 | "/* layer: default */ 78 | .zoom-in--0\\.1{--una-enter-scale:-0.001;} 79 | .zoom-in--10\\.1{--una-enter-scale:-0.101;} 80 | .zoom-in--180\\.37{--una-enter-scale:-1.8037;} 81 | .zoom-in--199\\.9{--una-enter-scale:-1.999;} 82 | .zoom-in--52\\.1{--una-enter-scale:-0.521;} 83 | .zoom-in--66\\.66{--una-enter-scale:-0.6666;} 84 | .zoom-in-0\\.1{--una-enter-scale:0.001;} 85 | .zoom-in-10\\.1{--una-enter-scale:0.101;} 86 | .zoom-in-180\\.37{--una-enter-scale:1.8037;} 87 | .zoom-in-199\\.9{--una-enter-scale:1.999;} 88 | .zoom-in-52\\.1{--una-enter-scale:0.521;} 89 | .zoom-in-66\\.66{--una-enter-scale:0.6666;} 90 | .zoom-in-99\\.9{--una-enter-scale:0.999;}" 91 | `) 92 | }) 93 | 94 | 95 | it(`should also convert both integers and decimals with "%" symbol`, async ({ expect }) => { 96 | const classnames = [ 97 | ...INTEGERS.map(i => `zoom-in-${i}%`), 98 | ...DECIMALS.map(i => `zoom-in-${i}%`), 99 | ] 100 | 101 | const { matched, css } = await uno.generate(classnames.join(' ')) 102 | 103 | expect(matched).toStrictEqual(new Set(classnames)) 104 | expect(css).toMatchInlineSnapshot(` 105 | "/* layer: default */ 106 | .zoom-in--0\\.1\\%{--una-enter-scale:-0.001;} 107 | .zoom-in--10\\.1\\%{--una-enter-scale:-0.101;} 108 | .zoom-in--10\\%{--una-enter-scale:-0.1;} 109 | .zoom-in--100\\%{--una-enter-scale:-1;} 110 | .zoom-in--110\\%{--una-enter-scale:-1.1;} 111 | .zoom-in--120\\%{--una-enter-scale:-1.2;} 112 | .zoom-in--130\\%{--una-enter-scale:-1.3;} 113 | .zoom-in--140\\%{--una-enter-scale:-1.4;} 114 | .zoom-in--150\\%{--una-enter-scale:-1.5;} 115 | .zoom-in--160\\%{--una-enter-scale:-1.6;} 116 | .zoom-in--170\\%{--una-enter-scale:-1.7;} 117 | .zoom-in--180\\.37\\%{--una-enter-scale:-1.8037;} 118 | .zoom-in--180\\%{--una-enter-scale:-1.8;} 119 | .zoom-in--190\\%{--una-enter-scale:-1.9;} 120 | .zoom-in--199\\.9\\%{--una-enter-scale:-1.999;} 121 | .zoom-in--20\\%{--una-enter-scale:-0.2;} 122 | .zoom-in--200\\%{--una-enter-scale:-2;} 123 | .zoom-in--30\\%{--una-enter-scale:-0.3;} 124 | .zoom-in--40\\%{--una-enter-scale:-0.4;} 125 | .zoom-in--50\\%{--una-enter-scale:-0.5;} 126 | .zoom-in--52\\.1\\%{--una-enter-scale:-0.521;} 127 | .zoom-in--60\\%{--una-enter-scale:-0.6;} 128 | .zoom-in--66\\.66\\%{--una-enter-scale:-0.6666;} 129 | .zoom-in--70\\%{--una-enter-scale:-0.7;} 130 | .zoom-in--80\\%{--una-enter-scale:-0.8;} 131 | .zoom-in--90\\%{--una-enter-scale:-0.9;} 132 | .zoom-in-0\\.1\\%{--una-enter-scale:0.001;} 133 | .zoom-in-0\\%{--una-enter-scale:0;} 134 | .zoom-in-10\\.1\\%{--una-enter-scale:0.101;} 135 | .zoom-in-10\\%{--una-enter-scale:0.1;} 136 | .zoom-in-100\\%{--una-enter-scale:1;} 137 | .zoom-in-110\\%{--una-enter-scale:1.1;} 138 | .zoom-in-120\\%{--una-enter-scale:1.2;} 139 | .zoom-in-130\\%{--una-enter-scale:1.3;} 140 | .zoom-in-140\\%{--una-enter-scale:1.4;} 141 | .zoom-in-150\\%{--una-enter-scale:1.5;} 142 | .zoom-in-160\\%{--una-enter-scale:1.6;} 143 | .zoom-in-170\\%{--una-enter-scale:1.7;} 144 | .zoom-in-180\\.37\\%{--una-enter-scale:1.8037;} 145 | .zoom-in-180\\%{--una-enter-scale:1.8;} 146 | .zoom-in-190\\%{--una-enter-scale:1.9;} 147 | .zoom-in-199\\.9\\%{--una-enter-scale:1.999;} 148 | .zoom-in-20\\%{--una-enter-scale:0.2;} 149 | .zoom-in-200\\%{--una-enter-scale:2;} 150 | .zoom-in-30\\%{--una-enter-scale:0.3;} 151 | .zoom-in-40\\%{--una-enter-scale:0.4;} 152 | .zoom-in-50\\%{--una-enter-scale:0.5;} 153 | .zoom-in-52\\.1\\%{--una-enter-scale:0.521;} 154 | .zoom-in-60\\%{--una-enter-scale:0.6;} 155 | .zoom-in-66\\.66\\%{--una-enter-scale:0.6666;} 156 | .zoom-in-70\\%{--una-enter-scale:0.7;} 157 | .zoom-in-80\\%{--una-enter-scale:0.8;} 158 | .zoom-in-90\\%{--una-enter-scale:0.9;} 159 | .zoom-in-99\\.9\\%{--una-enter-scale:0.999;}" 160 | `) 161 | }) 162 | }) 163 | 164 | 165 | describe('fraction', () => { 166 | it(`should convert any fractions including negative`, async ({ expect }) => { 167 | const classnames = FRACTIONS.map(i => `zoom-in-${i}`) 168 | 169 | const { matched, css } = await uno.generate(classnames.join(' ')) 170 | 171 | expect(matched).toStrictEqual(new Set(classnames)) 172 | expect(css).toMatchInlineSnapshot(` 173 | "/* layer: default */ 174 | .zoom-in--1\\/3{--una-enter-scale:-33.3333333333%;} 175 | .zoom-in--1\\/4{--una-enter-scale:-25%;} 176 | .zoom-in--1\\/6{--una-enter-scale:-16.6666666667%;} 177 | .zoom-in--2\\/3{--una-enter-scale:-66.6666666667%;} 178 | .zoom-in--3\\/4{--una-enter-scale:-75%;} 179 | .zoom-in--5\\/6{--una-enter-scale:-83.3333333333%;} 180 | .zoom-in-1\\/3{--una-enter-scale:33.3333333333%;} 181 | .zoom-in-1\\/4{--una-enter-scale:25%;} 182 | .zoom-in-1\\/6{--una-enter-scale:16.6666666667%;} 183 | .zoom-in-2\\/3{--una-enter-scale:66.6666666667%;} 184 | .zoom-in-3\\/4{--una-enter-scale:75%;} 185 | .zoom-in-5\\/6{--una-enter-scale:83.3333333333%;}" 186 | `) 187 | }) 188 | 189 | 190 | it(`should convert "full" to "100%`, async ({ expect }) => { 191 | const { css } = await uno.generate('zoom-in-full') 192 | 193 | expect(css).toMatchInlineSnapshot(` 194 | "/* layer: default */ 195 | .zoom-in-full{--una-enter-scale:100%;}" 196 | `) 197 | }) 198 | }) 199 | 200 | 201 | describe('css variable', () => { 202 | it(`should handle css variables`, async ({ expect }) => { 203 | const classnames = CSS_VARIABLES.map(i => `zoom-in-${i}`) 204 | 205 | const { matched, css } = await uno.generate(classnames.join(' ')) 206 | 207 | expect(matched).toStrictEqual(new Set(classnames)) 208 | expect(css).toMatchInlineSnapshot(` 209 | "/* layer: default */ 210 | .zoom-in-\\$foo{--una-enter-scale:var(--foo);} 211 | .zoom-in-\\$foo-bar{--una-enter-scale:var(--foo-bar);} 212 | .zoom-in-\\$fooBar{--una-enter-scale:var(--fooBar);}" 213 | `) 214 | }) 215 | }) 216 | }) 217 | 218 | 219 | describe('zoom-out', () => { 220 | it(`should generate "${CSS_VARIABLE_PREFIX}-exit-scale" css variable and default to "0"`, async ({ expect }) => { 221 | const { css } = await uno.generate('zoom-out') 222 | 223 | expect(css).toContain(`.zoom-out{${CSS_VARIABLE_PREFIX}-exit-scale:0;}`) 224 | }) 225 | 226 | 227 | describe('percentage', () => { 228 | it(`should convert any percentages including negative`, async ({ expect }) => { 229 | const classnames = INTEGERS.map(i => `zoom-out-${i}`) 230 | 231 | const { matched, css } = await uno.generate(classnames.join(' ')) 232 | 233 | expect(matched).toStrictEqual(new Set(classnames)) 234 | expect(css).toMatchInlineSnapshot(` 235 | "/* layer: default */ 236 | .zoom-out--10{--una-exit-scale:-0.1;} 237 | .zoom-out--100{--una-exit-scale:-1;} 238 | .zoom-out--110{--una-exit-scale:-1.1;} 239 | .zoom-out--120{--una-exit-scale:-1.2;} 240 | .zoom-out--130{--una-exit-scale:-1.3;} 241 | .zoom-out--140{--una-exit-scale:-1.4;} 242 | .zoom-out--150{--una-exit-scale:-1.5;} 243 | .zoom-out--160{--una-exit-scale:-1.6;} 244 | .zoom-out--170{--una-exit-scale:-1.7;} 245 | .zoom-out--180{--una-exit-scale:-1.8;} 246 | .zoom-out--190{--una-exit-scale:-1.9;} 247 | .zoom-out--20{--una-exit-scale:-0.2;} 248 | .zoom-out--200{--una-exit-scale:-2;} 249 | .zoom-out--30{--una-exit-scale:-0.3;} 250 | .zoom-out--40{--una-exit-scale:-0.4;} 251 | .zoom-out--50{--una-exit-scale:-0.5;} 252 | .zoom-out--60{--una-exit-scale:-0.6;} 253 | .zoom-out--70{--una-exit-scale:-0.7;} 254 | .zoom-out--80{--una-exit-scale:-0.8;} 255 | .zoom-out--90{--una-exit-scale:-0.9;} 256 | .zoom-out-0{--una-exit-scale:0;} 257 | .zoom-out-10{--una-exit-scale:0.1;} 258 | .zoom-out-100{--una-exit-scale:1;} 259 | .zoom-out-110{--una-exit-scale:1.1;} 260 | .zoom-out-120{--una-exit-scale:1.2;} 261 | .zoom-out-130{--una-exit-scale:1.3;} 262 | .zoom-out-140{--una-exit-scale:1.4;} 263 | .zoom-out-150{--una-exit-scale:1.5;} 264 | .zoom-out-160{--una-exit-scale:1.6;} 265 | .zoom-out-170{--una-exit-scale:1.7;} 266 | .zoom-out-180{--una-exit-scale:1.8;} 267 | .zoom-out-190{--una-exit-scale:1.9;} 268 | .zoom-out-20{--una-exit-scale:0.2;} 269 | .zoom-out-200{--una-exit-scale:2;} 270 | .zoom-out-30{--una-exit-scale:0.3;} 271 | .zoom-out-40{--una-exit-scale:0.4;} 272 | .zoom-out-50{--una-exit-scale:0.5;} 273 | .zoom-out-60{--una-exit-scale:0.6;} 274 | .zoom-out-70{--una-exit-scale:0.7;} 275 | .zoom-out-80{--una-exit-scale:0.8;} 276 | .zoom-out-90{--una-exit-scale:0.9;}" 277 | `) 278 | }) 279 | 280 | 281 | it(`should also convert decimals including negative`, async ({ expect }) => { 282 | const classnames = DECIMALS.map(i => `zoom-out-${i}`) 283 | 284 | const { matched, css } = await uno.generate(classnames.join(' ')) 285 | 286 | expect(matched).toStrictEqual(new Set(classnames)) 287 | expect(css).toMatchInlineSnapshot(` 288 | "/* layer: default */ 289 | .zoom-out--0\\.1{--una-exit-scale:-0.001;} 290 | .zoom-out--10\\.1{--una-exit-scale:-0.101;} 291 | .zoom-out--180\\.37{--una-exit-scale:-1.8037;} 292 | .zoom-out--199\\.9{--una-exit-scale:-1.999;} 293 | .zoom-out--52\\.1{--una-exit-scale:-0.521;} 294 | .zoom-out--66\\.66{--una-exit-scale:-0.6666;} 295 | .zoom-out-0\\.1{--una-exit-scale:0.001;} 296 | .zoom-out-10\\.1{--una-exit-scale:0.101;} 297 | .zoom-out-180\\.37{--una-exit-scale:1.8037;} 298 | .zoom-out-199\\.9{--una-exit-scale:1.999;} 299 | .zoom-out-52\\.1{--una-exit-scale:0.521;} 300 | .zoom-out-66\\.66{--una-exit-scale:0.6666;} 301 | .zoom-out-99\\.9{--una-exit-scale:0.999;}" 302 | `) 303 | }) 304 | 305 | 306 | it(`should also convert both integers and decimals with "%" symbol`, async ({ expect }) => { 307 | const classnames = [ 308 | ...INTEGERS.map(i => `zoom-out-${i}%`), 309 | ...DECIMALS.map(i => `zoom-out-${i}%`), 310 | ] 311 | 312 | const { matched, css } = await uno.generate(classnames.join(' ')) 313 | 314 | expect(matched).toStrictEqual(new Set(classnames)) 315 | expect(css).toMatchInlineSnapshot(` 316 | "/* layer: default */ 317 | .zoom-out--0\\.1\\%{--una-exit-scale:-0.001;} 318 | .zoom-out--10\\.1\\%{--una-exit-scale:-0.101;} 319 | .zoom-out--10\\%{--una-exit-scale:-0.1;} 320 | .zoom-out--100\\%{--una-exit-scale:-1;} 321 | .zoom-out--110\\%{--una-exit-scale:-1.1;} 322 | .zoom-out--120\\%{--una-exit-scale:-1.2;} 323 | .zoom-out--130\\%{--una-exit-scale:-1.3;} 324 | .zoom-out--140\\%{--una-exit-scale:-1.4;} 325 | .zoom-out--150\\%{--una-exit-scale:-1.5;} 326 | .zoom-out--160\\%{--una-exit-scale:-1.6;} 327 | .zoom-out--170\\%{--una-exit-scale:-1.7;} 328 | .zoom-out--180\\.37\\%{--una-exit-scale:-1.8037;} 329 | .zoom-out--180\\%{--una-exit-scale:-1.8;} 330 | .zoom-out--190\\%{--una-exit-scale:-1.9;} 331 | .zoom-out--199\\.9\\%{--una-exit-scale:-1.999;} 332 | .zoom-out--20\\%{--una-exit-scale:-0.2;} 333 | .zoom-out--200\\%{--una-exit-scale:-2;} 334 | .zoom-out--30\\%{--una-exit-scale:-0.3;} 335 | .zoom-out--40\\%{--una-exit-scale:-0.4;} 336 | .zoom-out--50\\%{--una-exit-scale:-0.5;} 337 | .zoom-out--52\\.1\\%{--una-exit-scale:-0.521;} 338 | .zoom-out--60\\%{--una-exit-scale:-0.6;} 339 | .zoom-out--66\\.66\\%{--una-exit-scale:-0.6666;} 340 | .zoom-out--70\\%{--una-exit-scale:-0.7;} 341 | .zoom-out--80\\%{--una-exit-scale:-0.8;} 342 | .zoom-out--90\\%{--una-exit-scale:-0.9;} 343 | .zoom-out-0\\.1\\%{--una-exit-scale:0.001;} 344 | .zoom-out-0\\%{--una-exit-scale:0;} 345 | .zoom-out-10\\.1\\%{--una-exit-scale:0.101;} 346 | .zoom-out-10\\%{--una-exit-scale:0.1;} 347 | .zoom-out-100\\%{--una-exit-scale:1;} 348 | .zoom-out-110\\%{--una-exit-scale:1.1;} 349 | .zoom-out-120\\%{--una-exit-scale:1.2;} 350 | .zoom-out-130\\%{--una-exit-scale:1.3;} 351 | .zoom-out-140\\%{--una-exit-scale:1.4;} 352 | .zoom-out-150\\%{--una-exit-scale:1.5;} 353 | .zoom-out-160\\%{--una-exit-scale:1.6;} 354 | .zoom-out-170\\%{--una-exit-scale:1.7;} 355 | .zoom-out-180\\.37\\%{--una-exit-scale:1.8037;} 356 | .zoom-out-180\\%{--una-exit-scale:1.8;} 357 | .zoom-out-190\\%{--una-exit-scale:1.9;} 358 | .zoom-out-199\\.9\\%{--una-exit-scale:1.999;} 359 | .zoom-out-20\\%{--una-exit-scale:0.2;} 360 | .zoom-out-200\\%{--una-exit-scale:2;} 361 | .zoom-out-30\\%{--una-exit-scale:0.3;} 362 | .zoom-out-40\\%{--una-exit-scale:0.4;} 363 | .zoom-out-50\\%{--una-exit-scale:0.5;} 364 | .zoom-out-52\\.1\\%{--una-exit-scale:0.521;} 365 | .zoom-out-60\\%{--una-exit-scale:0.6;} 366 | .zoom-out-66\\.66\\%{--una-exit-scale:0.6666;} 367 | .zoom-out-70\\%{--una-exit-scale:0.7;} 368 | .zoom-out-80\\%{--una-exit-scale:0.8;} 369 | .zoom-out-90\\%{--una-exit-scale:0.9;} 370 | .zoom-out-99\\.9\\%{--una-exit-scale:0.999;}" 371 | `) 372 | }) 373 | }) 374 | 375 | 376 | describe('fraction', () => { 377 | it(`should convert any fractions including negative`, async ({ expect }) => { 378 | const classnames = FRACTIONS.map(i => `zoom-out-${i}`) 379 | 380 | const { matched, css } = await uno.generate(classnames.join(' ')) 381 | 382 | expect(matched).toStrictEqual(new Set(classnames)) 383 | expect(css).toMatchInlineSnapshot(` 384 | "/* layer: default */ 385 | .zoom-out--1\\/3{--una-exit-scale:-33.3333333333%;} 386 | .zoom-out--1\\/4{--una-exit-scale:-25%;} 387 | .zoom-out--1\\/6{--una-exit-scale:-16.6666666667%;} 388 | .zoom-out--2\\/3{--una-exit-scale:-66.6666666667%;} 389 | .zoom-out--3\\/4{--una-exit-scale:-75%;} 390 | .zoom-out--5\\/6{--una-exit-scale:-83.3333333333%;} 391 | .zoom-out-1\\/3{--una-exit-scale:33.3333333333%;} 392 | .zoom-out-1\\/4{--una-exit-scale:25%;} 393 | .zoom-out-1\\/6{--una-exit-scale:16.6666666667%;} 394 | .zoom-out-2\\/3{--una-exit-scale:66.6666666667%;} 395 | .zoom-out-3\\/4{--una-exit-scale:75%;} 396 | .zoom-out-5\\/6{--una-exit-scale:83.3333333333%;}" 397 | `) 398 | }) 399 | 400 | 401 | it(`should convert "full" to "100%`, async ({ expect }) => { 402 | const { css } = await uno.generate('zoom-out-full') 403 | 404 | expect(css).toMatchInlineSnapshot(` 405 | "/* layer: default */ 406 | .zoom-out-full{--una-exit-scale:100%;}" 407 | `) 408 | }) 409 | }) 410 | 411 | 412 | describe('css variable', () => { 413 | it(`should handle css variables`, async ({ expect }) => { 414 | const classnames = CSS_VARIABLES.map(i => `zoom-out-${i}`) 415 | 416 | const { matched, css } = await uno.generate(classnames.join(' ')) 417 | 418 | expect(matched).toStrictEqual(new Set(classnames)) 419 | expect(css).toMatchInlineSnapshot(` 420 | "/* layer: default */ 421 | .zoom-out-\\$foo{--una-exit-scale:var(--foo);} 422 | .zoom-out-\\$foo-bar{--una-exit-scale:var(--foo-bar);} 423 | .zoom-out-\\$fooBar{--una-exit-scale:var(--fooBar);}" 424 | `) 425 | }) 426 | }) 427 | }) 428 | }) 429 | -------------------------------------------------------------------------------- /test/spin.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { CSS_VARIABLE_PREFIX } from '@/constants' 3 | import { CSS_VARIABLES, DECIMALS, INTEGERS } from '~/data' 4 | import { uno } from '~/utils' 5 | 6 | 7 | describe.concurrent('spin animation', () => { 8 | describe('spin-in', () => { 9 | it(`should generate "${CSS_VARIABLE_PREFIX}-enter-rotate" css variable and default to "30deg"`, async ({ expect }) => { 10 | const { css } = await uno.generate('spin-in') 11 | 12 | expect(css).toContain(`.spin-in{${CSS_VARIABLE_PREFIX}-enter-rotate:30deg;}`) 13 | }) 14 | 15 | 16 | describe('angle', () => { 17 | it(`should handle any numbers including negative and unit default to "deg"`, async ({ expect }) => { 18 | const classnames = INTEGERS.map(i => `spin-in-${i}`) 19 | 20 | const { matched, css } = await uno.generate(classnames.join(' ')) 21 | 22 | expect(matched).toStrictEqual(new Set(classnames)) 23 | expect(css).toMatchInlineSnapshot(` 24 | "/* layer: default */ 25 | .spin-in--10{--una-enter-rotate:-10deg;} 26 | .spin-in--100{--una-enter-rotate:-100deg;} 27 | .spin-in--110{--una-enter-rotate:-110deg;} 28 | .spin-in--120{--una-enter-rotate:-120deg;} 29 | .spin-in--130{--una-enter-rotate:-130deg;} 30 | .spin-in--140{--una-enter-rotate:-140deg;} 31 | .spin-in--150{--una-enter-rotate:-150deg;} 32 | .spin-in--160{--una-enter-rotate:-160deg;} 33 | .spin-in--170{--una-enter-rotate:-170deg;} 34 | .spin-in--180{--una-enter-rotate:-180deg;} 35 | .spin-in--190{--una-enter-rotate:-190deg;} 36 | .spin-in--20{--una-enter-rotate:-20deg;} 37 | .spin-in--200{--una-enter-rotate:-200deg;} 38 | .spin-in--30{--una-enter-rotate:-30deg;} 39 | .spin-in--40{--una-enter-rotate:-40deg;} 40 | .spin-in--50{--una-enter-rotate:-50deg;} 41 | .spin-in--60{--una-enter-rotate:-60deg;} 42 | .spin-in--70{--una-enter-rotate:-70deg;} 43 | .spin-in--80{--una-enter-rotate:-80deg;} 44 | .spin-in--90{--una-enter-rotate:-90deg;} 45 | .spin-in-0{--una-enter-rotate:0deg;} 46 | .spin-in-10{--una-enter-rotate:10deg;} 47 | .spin-in-100{--una-enter-rotate:100deg;} 48 | .spin-in-110{--una-enter-rotate:110deg;} 49 | .spin-in-120{--una-enter-rotate:120deg;} 50 | .spin-in-130{--una-enter-rotate:130deg;} 51 | .spin-in-140{--una-enter-rotate:140deg;} 52 | .spin-in-150{--una-enter-rotate:150deg;} 53 | .spin-in-160{--una-enter-rotate:160deg;} 54 | .spin-in-170{--una-enter-rotate:170deg;} 55 | .spin-in-180{--una-enter-rotate:180deg;} 56 | .spin-in-190{--una-enter-rotate:190deg;} 57 | .spin-in-20{--una-enter-rotate:20deg;} 58 | .spin-in-200{--una-enter-rotate:200deg;} 59 | .spin-in-30{--una-enter-rotate:30deg;} 60 | .spin-in-40{--una-enter-rotate:40deg;} 61 | .spin-in-50{--una-enter-rotate:50deg;} 62 | .spin-in-60{--una-enter-rotate:60deg;} 63 | .spin-in-70{--una-enter-rotate:70deg;} 64 | .spin-in-80{--una-enter-rotate:80deg;} 65 | .spin-in-90{--una-enter-rotate:90deg;}" 66 | `) 67 | }) 68 | 69 | 70 | it(`should also handle decimals including negative`, async ({ expect }) => { 71 | const classnames = DECIMALS.map(i => `spin-in-${i}`) 72 | 73 | const { matched, css } = await uno.generate(classnames.join(' ')) 74 | 75 | expect(matched).toStrictEqual(new Set(classnames)) 76 | expect(css).toMatchInlineSnapshot(` 77 | "/* layer: default */ 78 | .spin-in--0\\.1{--una-enter-rotate:-0.1deg;} 79 | .spin-in--10\\.1{--una-enter-rotate:-10.1deg;} 80 | .spin-in--180\\.37{--una-enter-rotate:-180.37deg;} 81 | .spin-in--199\\.9{--una-enter-rotate:-199.9deg;} 82 | .spin-in--52\\.1{--una-enter-rotate:-52.1deg;} 83 | .spin-in--66\\.66{--una-enter-rotate:-66.66deg;} 84 | .spin-in-0\\.1{--una-enter-rotate:0.1deg;} 85 | .spin-in-10\\.1{--una-enter-rotate:10.1deg;} 86 | .spin-in-180\\.37{--una-enter-rotate:180.37deg;} 87 | .spin-in-199\\.9{--una-enter-rotate:199.9deg;} 88 | .spin-in-52\\.1{--una-enter-rotate:52.1deg;} 89 | .spin-in-66\\.66{--una-enter-rotate:66.66deg;} 90 | .spin-in-99\\.9{--una-enter-rotate:99.9deg;}" 91 | `) 92 | }) 93 | 94 | 95 | it(`should use units ("deg", "rad", "grad", "turn") as is`, async ({ expect }) => { 96 | const DATASET = INTEGERS.filter(Boolean) 97 | 98 | const classnames = [ 99 | ...DATASET.map(i => `spin-in-${i}deg`), 100 | ...DATASET.map(i => `spin-in-${i}rad`), 101 | ...DATASET.map(i => `spin-in-${i}grad`), 102 | ...DATASET.map(i => `spin-in-${i}turn`), 103 | ] 104 | 105 | const { matched, css } = await uno.generate(classnames.join(' ')) 106 | 107 | expect(matched).toStrictEqual(new Set(classnames)) 108 | expect(css).toMatchInlineSnapshot(` 109 | "/* layer: default */ 110 | .spin-in--100deg{--una-enter-rotate:-100deg;} 111 | .spin-in--100grad{--una-enter-rotate:-100grad;} 112 | .spin-in--100rad{--una-enter-rotate:-100rad;} 113 | .spin-in--100turn{--una-enter-rotate:-100turn;} 114 | .spin-in--10deg{--una-enter-rotate:-10deg;} 115 | .spin-in--10grad{--una-enter-rotate:-10grad;} 116 | .spin-in--10rad{--una-enter-rotate:-10rad;} 117 | .spin-in--10turn{--una-enter-rotate:-10turn;} 118 | .spin-in--110deg{--una-enter-rotate:-110deg;} 119 | .spin-in--110grad{--una-enter-rotate:-110grad;} 120 | .spin-in--110rad{--una-enter-rotate:-110rad;} 121 | .spin-in--110turn{--una-enter-rotate:-110turn;} 122 | .spin-in--120deg{--una-enter-rotate:-120deg;} 123 | .spin-in--120grad{--una-enter-rotate:-120grad;} 124 | .spin-in--120rad{--una-enter-rotate:-120rad;} 125 | .spin-in--120turn{--una-enter-rotate:-120turn;} 126 | .spin-in--130deg{--una-enter-rotate:-130deg;} 127 | .spin-in--130grad{--una-enter-rotate:-130grad;} 128 | .spin-in--130rad{--una-enter-rotate:-130rad;} 129 | .spin-in--130turn{--una-enter-rotate:-130turn;} 130 | .spin-in--140deg{--una-enter-rotate:-140deg;} 131 | .spin-in--140grad{--una-enter-rotate:-140grad;} 132 | .spin-in--140rad{--una-enter-rotate:-140rad;} 133 | .spin-in--140turn{--una-enter-rotate:-140turn;} 134 | .spin-in--150deg{--una-enter-rotate:-150deg;} 135 | .spin-in--150grad{--una-enter-rotate:-150grad;} 136 | .spin-in--150rad{--una-enter-rotate:-150rad;} 137 | .spin-in--150turn{--una-enter-rotate:-150turn;} 138 | .spin-in--160deg{--una-enter-rotate:-160deg;} 139 | .spin-in--160grad{--una-enter-rotate:-160grad;} 140 | .spin-in--160rad{--una-enter-rotate:-160rad;} 141 | .spin-in--160turn{--una-enter-rotate:-160turn;} 142 | .spin-in--170deg{--una-enter-rotate:-170deg;} 143 | .spin-in--170grad{--una-enter-rotate:-170grad;} 144 | .spin-in--170rad{--una-enter-rotate:-170rad;} 145 | .spin-in--170turn{--una-enter-rotate:-170turn;} 146 | .spin-in--180deg{--una-enter-rotate:-180deg;} 147 | .spin-in--180grad{--una-enter-rotate:-180grad;} 148 | .spin-in--180rad{--una-enter-rotate:-180rad;} 149 | .spin-in--180turn{--una-enter-rotate:-180turn;} 150 | .spin-in--190deg{--una-enter-rotate:-190deg;} 151 | .spin-in--190grad{--una-enter-rotate:-190grad;} 152 | .spin-in--190rad{--una-enter-rotate:-190rad;} 153 | .spin-in--190turn{--una-enter-rotate:-190turn;} 154 | .spin-in--200deg{--una-enter-rotate:-200deg;} 155 | .spin-in--200grad{--una-enter-rotate:-200grad;} 156 | .spin-in--200rad{--una-enter-rotate:-200rad;} 157 | .spin-in--200turn{--una-enter-rotate:-200turn;} 158 | .spin-in--20deg{--una-enter-rotate:-20deg;} 159 | .spin-in--20grad{--una-enter-rotate:-20grad;} 160 | .spin-in--20rad{--una-enter-rotate:-20rad;} 161 | .spin-in--20turn{--una-enter-rotate:-20turn;} 162 | .spin-in--30deg{--una-enter-rotate:-30deg;} 163 | .spin-in--30grad{--una-enter-rotate:-30grad;} 164 | .spin-in--30rad{--una-enter-rotate:-30rad;} 165 | .spin-in--30turn{--una-enter-rotate:-30turn;} 166 | .spin-in--40deg{--una-enter-rotate:-40deg;} 167 | .spin-in--40grad{--una-enter-rotate:-40grad;} 168 | .spin-in--40rad{--una-enter-rotate:-40rad;} 169 | .spin-in--40turn{--una-enter-rotate:-40turn;} 170 | .spin-in--50deg{--una-enter-rotate:-50deg;} 171 | .spin-in--50grad{--una-enter-rotate:-50grad;} 172 | .spin-in--50rad{--una-enter-rotate:-50rad;} 173 | .spin-in--50turn{--una-enter-rotate:-50turn;} 174 | .spin-in--60deg{--una-enter-rotate:-60deg;} 175 | .spin-in--60grad{--una-enter-rotate:-60grad;} 176 | .spin-in--60rad{--una-enter-rotate:-60rad;} 177 | .spin-in--60turn{--una-enter-rotate:-60turn;} 178 | .spin-in--70deg{--una-enter-rotate:-70deg;} 179 | .spin-in--70grad{--una-enter-rotate:-70grad;} 180 | .spin-in--70rad{--una-enter-rotate:-70rad;} 181 | .spin-in--70turn{--una-enter-rotate:-70turn;} 182 | .spin-in--80deg{--una-enter-rotate:-80deg;} 183 | .spin-in--80grad{--una-enter-rotate:-80grad;} 184 | .spin-in--80rad{--una-enter-rotate:-80rad;} 185 | .spin-in--80turn{--una-enter-rotate:-80turn;} 186 | .spin-in--90deg{--una-enter-rotate:-90deg;} 187 | .spin-in--90grad{--una-enter-rotate:-90grad;} 188 | .spin-in--90rad{--una-enter-rotate:-90rad;} 189 | .spin-in--90turn{--una-enter-rotate:-90turn;} 190 | .spin-in-100deg{--una-enter-rotate:100deg;} 191 | .spin-in-100grad{--una-enter-rotate:100grad;} 192 | .spin-in-100rad{--una-enter-rotate:100rad;} 193 | .spin-in-100turn{--una-enter-rotate:100turn;} 194 | .spin-in-10deg{--una-enter-rotate:10deg;} 195 | .spin-in-10grad{--una-enter-rotate:10grad;} 196 | .spin-in-10rad{--una-enter-rotate:10rad;} 197 | .spin-in-10turn{--una-enter-rotate:10turn;} 198 | .spin-in-110deg{--una-enter-rotate:110deg;} 199 | .spin-in-110grad{--una-enter-rotate:110grad;} 200 | .spin-in-110rad{--una-enter-rotate:110rad;} 201 | .spin-in-110turn{--una-enter-rotate:110turn;} 202 | .spin-in-120deg{--una-enter-rotate:120deg;} 203 | .spin-in-120grad{--una-enter-rotate:120grad;} 204 | .spin-in-120rad{--una-enter-rotate:120rad;} 205 | .spin-in-120turn{--una-enter-rotate:120turn;} 206 | .spin-in-130deg{--una-enter-rotate:130deg;} 207 | .spin-in-130grad{--una-enter-rotate:130grad;} 208 | .spin-in-130rad{--una-enter-rotate:130rad;} 209 | .spin-in-130turn{--una-enter-rotate:130turn;} 210 | .spin-in-140deg{--una-enter-rotate:140deg;} 211 | .spin-in-140grad{--una-enter-rotate:140grad;} 212 | .spin-in-140rad{--una-enter-rotate:140rad;} 213 | .spin-in-140turn{--una-enter-rotate:140turn;} 214 | .spin-in-150deg{--una-enter-rotate:150deg;} 215 | .spin-in-150grad{--una-enter-rotate:150grad;} 216 | .spin-in-150rad{--una-enter-rotate:150rad;} 217 | .spin-in-150turn{--una-enter-rotate:150turn;} 218 | .spin-in-160deg{--una-enter-rotate:160deg;} 219 | .spin-in-160grad{--una-enter-rotate:160grad;} 220 | .spin-in-160rad{--una-enter-rotate:160rad;} 221 | .spin-in-160turn{--una-enter-rotate:160turn;} 222 | .spin-in-170deg{--una-enter-rotate:170deg;} 223 | .spin-in-170grad{--una-enter-rotate:170grad;} 224 | .spin-in-170rad{--una-enter-rotate:170rad;} 225 | .spin-in-170turn{--una-enter-rotate:170turn;} 226 | .spin-in-180deg{--una-enter-rotate:180deg;} 227 | .spin-in-180grad{--una-enter-rotate:180grad;} 228 | .spin-in-180rad{--una-enter-rotate:180rad;} 229 | .spin-in-180turn{--una-enter-rotate:180turn;} 230 | .spin-in-190deg{--una-enter-rotate:190deg;} 231 | .spin-in-190grad{--una-enter-rotate:190grad;} 232 | .spin-in-190rad{--una-enter-rotate:190rad;} 233 | .spin-in-190turn{--una-enter-rotate:190turn;} 234 | .spin-in-200deg{--una-enter-rotate:200deg;} 235 | .spin-in-200grad{--una-enter-rotate:200grad;} 236 | .spin-in-200rad{--una-enter-rotate:200rad;} 237 | .spin-in-200turn{--una-enter-rotate:200turn;} 238 | .spin-in-20deg{--una-enter-rotate:20deg;} 239 | .spin-in-20grad{--una-enter-rotate:20grad;} 240 | .spin-in-20rad{--una-enter-rotate:20rad;} 241 | .spin-in-20turn{--una-enter-rotate:20turn;} 242 | .spin-in-30deg{--una-enter-rotate:30deg;} 243 | .spin-in-30grad{--una-enter-rotate:30grad;} 244 | .spin-in-30rad{--una-enter-rotate:30rad;} 245 | .spin-in-30turn{--una-enter-rotate:30turn;} 246 | .spin-in-40deg{--una-enter-rotate:40deg;} 247 | .spin-in-40grad{--una-enter-rotate:40grad;} 248 | .spin-in-40rad{--una-enter-rotate:40rad;} 249 | .spin-in-40turn{--una-enter-rotate:40turn;} 250 | .spin-in-50deg{--una-enter-rotate:50deg;} 251 | .spin-in-50grad{--una-enter-rotate:50grad;} 252 | .spin-in-50rad{--una-enter-rotate:50rad;} 253 | .spin-in-50turn{--una-enter-rotate:50turn;} 254 | .spin-in-60deg{--una-enter-rotate:60deg;} 255 | .spin-in-60grad{--una-enter-rotate:60grad;} 256 | .spin-in-60rad{--una-enter-rotate:60rad;} 257 | .spin-in-60turn{--una-enter-rotate:60turn;} 258 | .spin-in-70deg{--una-enter-rotate:70deg;} 259 | .spin-in-70grad{--una-enter-rotate:70grad;} 260 | .spin-in-70rad{--una-enter-rotate:70rad;} 261 | .spin-in-70turn{--una-enter-rotate:70turn;} 262 | .spin-in-80deg{--una-enter-rotate:80deg;} 263 | .spin-in-80grad{--una-enter-rotate:80grad;} 264 | .spin-in-80rad{--una-enter-rotate:80rad;} 265 | .spin-in-80turn{--una-enter-rotate:80turn;} 266 | .spin-in-90deg{--una-enter-rotate:90deg;} 267 | .spin-in-90grad{--una-enter-rotate:90grad;} 268 | .spin-in-90rad{--una-enter-rotate:90rad;} 269 | .spin-in-90turn{--una-enter-rotate:90turn;}" 270 | `) 271 | }) 272 | 273 | 274 | it(`should not use any unit for "0"`, async ({ expect }) => { 275 | const classnames = [ 276 | 'spin-in-0', 277 | 'spin-in-0deg', 278 | 'spin-in-0rad', 279 | 'spin-in-0grad', 280 | 'spin-in-0turn', 281 | ] 282 | 283 | const { matched, css } = await uno.generate(classnames) 284 | 285 | expect(matched).toStrictEqual(new Set(classnames)) 286 | expect(css).toMatchInlineSnapshot(` 287 | "/* layer: default */ 288 | .spin-in-0, 289 | .spin-in-0deg, 290 | .spin-in-0grad, 291 | .spin-in-0rad, 292 | .spin-in-0turn{--una-enter-rotate:0deg;}" 293 | `) 294 | }) 295 | }) 296 | 297 | 298 | describe('css variable', () => { 299 | it(`should handle css variables`, async ({ expect }) => { 300 | const classnames = CSS_VARIABLES.map(i => `spin-in-${i}`) 301 | 302 | const { matched, css } = await uno.generate(classnames.join(' ')) 303 | 304 | expect(matched).toStrictEqual(new Set(classnames)) 305 | expect(css).toMatchInlineSnapshot(` 306 | "/* layer: default */ 307 | .spin-in-\\$foo{--una-enter-rotate:var(--foo);} 308 | .spin-in-\\$foo-bar{--una-enter-rotate:var(--foo-bar);} 309 | .spin-in-\\$fooBar{--una-enter-rotate:var(--fooBar);}" 310 | `) 311 | }) 312 | }) 313 | }) 314 | 315 | 316 | describe('spin-out', () => { 317 | it(`should generate "${CSS_VARIABLE_PREFIX}-exit-rotate" css variable and default to "30deg"`, async ({ expect }) => { 318 | const { css } = await uno.generate('spin-out') 319 | 320 | expect(css).toContain(`.spin-out{${CSS_VARIABLE_PREFIX}-exit-rotate:30deg;}`) 321 | }) 322 | 323 | 324 | describe('angle', () => { 325 | it(`should handle any numbers including negative and unit default to "deg"`, async ({ expect }) => { 326 | const classnames = INTEGERS.map(i => `spin-out-${i}`) 327 | 328 | const { matched, css } = await uno.generate(classnames.join(' ')) 329 | 330 | expect(matched).toStrictEqual(new Set(classnames)) 331 | expect(css).toMatchInlineSnapshot(` 332 | "/* layer: default */ 333 | .spin-out--10{--una-exit-rotate:-10deg;} 334 | .spin-out--100{--una-exit-rotate:-100deg;} 335 | .spin-out--110{--una-exit-rotate:-110deg;} 336 | .spin-out--120{--una-exit-rotate:-120deg;} 337 | .spin-out--130{--una-exit-rotate:-130deg;} 338 | .spin-out--140{--una-exit-rotate:-140deg;} 339 | .spin-out--150{--una-exit-rotate:-150deg;} 340 | .spin-out--160{--una-exit-rotate:-160deg;} 341 | .spin-out--170{--una-exit-rotate:-170deg;} 342 | .spin-out--180{--una-exit-rotate:-180deg;} 343 | .spin-out--190{--una-exit-rotate:-190deg;} 344 | .spin-out--20{--una-exit-rotate:-20deg;} 345 | .spin-out--200{--una-exit-rotate:-200deg;} 346 | .spin-out--30{--una-exit-rotate:-30deg;} 347 | .spin-out--40{--una-exit-rotate:-40deg;} 348 | .spin-out--50{--una-exit-rotate:-50deg;} 349 | .spin-out--60{--una-exit-rotate:-60deg;} 350 | .spin-out--70{--una-exit-rotate:-70deg;} 351 | .spin-out--80{--una-exit-rotate:-80deg;} 352 | .spin-out--90{--una-exit-rotate:-90deg;} 353 | .spin-out-0{--una-exit-rotate:0deg;} 354 | .spin-out-10{--una-exit-rotate:10deg;} 355 | .spin-out-100{--una-exit-rotate:100deg;} 356 | .spin-out-110{--una-exit-rotate:110deg;} 357 | .spin-out-120{--una-exit-rotate:120deg;} 358 | .spin-out-130{--una-exit-rotate:130deg;} 359 | .spin-out-140{--una-exit-rotate:140deg;} 360 | .spin-out-150{--una-exit-rotate:150deg;} 361 | .spin-out-160{--una-exit-rotate:160deg;} 362 | .spin-out-170{--una-exit-rotate:170deg;} 363 | .spin-out-180{--una-exit-rotate:180deg;} 364 | .spin-out-190{--una-exit-rotate:190deg;} 365 | .spin-out-20{--una-exit-rotate:20deg;} 366 | .spin-out-200{--una-exit-rotate:200deg;} 367 | .spin-out-30{--una-exit-rotate:30deg;} 368 | .spin-out-40{--una-exit-rotate:40deg;} 369 | .spin-out-50{--una-exit-rotate:50deg;} 370 | .spin-out-60{--una-exit-rotate:60deg;} 371 | .spin-out-70{--una-exit-rotate:70deg;} 372 | .spin-out-80{--una-exit-rotate:80deg;} 373 | .spin-out-90{--una-exit-rotate:90deg;}" 374 | `) 375 | }) 376 | 377 | 378 | it(`should also handle decimals including negative`, async ({ expect }) => { 379 | const classnames = DECIMALS.map(i => `spin-out-${i}`) 380 | 381 | const { css } = await uno.generate(classnames.join(' ')) 382 | 383 | expect(css).toMatchInlineSnapshot(` 384 | "/* layer: default */ 385 | .spin-out--0\\.1{--una-exit-rotate:-0.1deg;} 386 | .spin-out--10\\.1{--una-exit-rotate:-10.1deg;} 387 | .spin-out--180\\.37{--una-exit-rotate:-180.37deg;} 388 | .spin-out--199\\.9{--una-exit-rotate:-199.9deg;} 389 | .spin-out--52\\.1{--una-exit-rotate:-52.1deg;} 390 | .spin-out--66\\.66{--una-exit-rotate:-66.66deg;} 391 | .spin-out-0\\.1{--una-exit-rotate:0.1deg;} 392 | .spin-out-10\\.1{--una-exit-rotate:10.1deg;} 393 | .spin-out-180\\.37{--una-exit-rotate:180.37deg;} 394 | .spin-out-199\\.9{--una-exit-rotate:199.9deg;} 395 | .spin-out-52\\.1{--una-exit-rotate:52.1deg;} 396 | .spin-out-66\\.66{--una-exit-rotate:66.66deg;} 397 | .spin-out-99\\.9{--una-exit-rotate:99.9deg;}" 398 | `) 399 | }) 400 | 401 | 402 | it(`should use units ("deg", "rad", "grad", "turn") as is`, async ({ expect }) => { 403 | const DATASET = INTEGERS.filter(Boolean) 404 | 405 | const classnames = [ 406 | ...DATASET.map(i => `spin-out-${i}deg`), 407 | ...DATASET.map(i => `spin-out-${i}rad`), 408 | ...DATASET.map(i => `spin-out-${i}grad`), 409 | ...DATASET.map(i => `spin-out-${i}turn`), 410 | ] 411 | 412 | const { matched, css } = await uno.generate(classnames.join(' ')) 413 | 414 | expect(matched).toStrictEqual(new Set(classnames)) 415 | expect(css).toMatchInlineSnapshot(` 416 | "/* layer: default */ 417 | .spin-out--100deg{--una-exit-rotate:-100deg;} 418 | .spin-out--100grad{--una-exit-rotate:-100grad;} 419 | .spin-out--100rad{--una-exit-rotate:-100rad;} 420 | .spin-out--100turn{--una-exit-rotate:-100turn;} 421 | .spin-out--10deg{--una-exit-rotate:-10deg;} 422 | .spin-out--10grad{--una-exit-rotate:-10grad;} 423 | .spin-out--10rad{--una-exit-rotate:-10rad;} 424 | .spin-out--10turn{--una-exit-rotate:-10turn;} 425 | .spin-out--110deg{--una-exit-rotate:-110deg;} 426 | .spin-out--110grad{--una-exit-rotate:-110grad;} 427 | .spin-out--110rad{--una-exit-rotate:-110rad;} 428 | .spin-out--110turn{--una-exit-rotate:-110turn;} 429 | .spin-out--120deg{--una-exit-rotate:-120deg;} 430 | .spin-out--120grad{--una-exit-rotate:-120grad;} 431 | .spin-out--120rad{--una-exit-rotate:-120rad;} 432 | .spin-out--120turn{--una-exit-rotate:-120turn;} 433 | .spin-out--130deg{--una-exit-rotate:-130deg;} 434 | .spin-out--130grad{--una-exit-rotate:-130grad;} 435 | .spin-out--130rad{--una-exit-rotate:-130rad;} 436 | .spin-out--130turn{--una-exit-rotate:-130turn;} 437 | .spin-out--140deg{--una-exit-rotate:-140deg;} 438 | .spin-out--140grad{--una-exit-rotate:-140grad;} 439 | .spin-out--140rad{--una-exit-rotate:-140rad;} 440 | .spin-out--140turn{--una-exit-rotate:-140turn;} 441 | .spin-out--150deg{--una-exit-rotate:-150deg;} 442 | .spin-out--150grad{--una-exit-rotate:-150grad;} 443 | .spin-out--150rad{--una-exit-rotate:-150rad;} 444 | .spin-out--150turn{--una-exit-rotate:-150turn;} 445 | .spin-out--160deg{--una-exit-rotate:-160deg;} 446 | .spin-out--160grad{--una-exit-rotate:-160grad;} 447 | .spin-out--160rad{--una-exit-rotate:-160rad;} 448 | .spin-out--160turn{--una-exit-rotate:-160turn;} 449 | .spin-out--170deg{--una-exit-rotate:-170deg;} 450 | .spin-out--170grad{--una-exit-rotate:-170grad;} 451 | .spin-out--170rad{--una-exit-rotate:-170rad;} 452 | .spin-out--170turn{--una-exit-rotate:-170turn;} 453 | .spin-out--180deg{--una-exit-rotate:-180deg;} 454 | .spin-out--180grad{--una-exit-rotate:-180grad;} 455 | .spin-out--180rad{--una-exit-rotate:-180rad;} 456 | .spin-out--180turn{--una-exit-rotate:-180turn;} 457 | .spin-out--190deg{--una-exit-rotate:-190deg;} 458 | .spin-out--190grad{--una-exit-rotate:-190grad;} 459 | .spin-out--190rad{--una-exit-rotate:-190rad;} 460 | .spin-out--190turn{--una-exit-rotate:-190turn;} 461 | .spin-out--200deg{--una-exit-rotate:-200deg;} 462 | .spin-out--200grad{--una-exit-rotate:-200grad;} 463 | .spin-out--200rad{--una-exit-rotate:-200rad;} 464 | .spin-out--200turn{--una-exit-rotate:-200turn;} 465 | .spin-out--20deg{--una-exit-rotate:-20deg;} 466 | .spin-out--20grad{--una-exit-rotate:-20grad;} 467 | .spin-out--20rad{--una-exit-rotate:-20rad;} 468 | .spin-out--20turn{--una-exit-rotate:-20turn;} 469 | .spin-out--30deg{--una-exit-rotate:-30deg;} 470 | .spin-out--30grad{--una-exit-rotate:-30grad;} 471 | .spin-out--30rad{--una-exit-rotate:-30rad;} 472 | .spin-out--30turn{--una-exit-rotate:-30turn;} 473 | .spin-out--40deg{--una-exit-rotate:-40deg;} 474 | .spin-out--40grad{--una-exit-rotate:-40grad;} 475 | .spin-out--40rad{--una-exit-rotate:-40rad;} 476 | .spin-out--40turn{--una-exit-rotate:-40turn;} 477 | .spin-out--50deg{--una-exit-rotate:-50deg;} 478 | .spin-out--50grad{--una-exit-rotate:-50grad;} 479 | .spin-out--50rad{--una-exit-rotate:-50rad;} 480 | .spin-out--50turn{--una-exit-rotate:-50turn;} 481 | .spin-out--60deg{--una-exit-rotate:-60deg;} 482 | .spin-out--60grad{--una-exit-rotate:-60grad;} 483 | .spin-out--60rad{--una-exit-rotate:-60rad;} 484 | .spin-out--60turn{--una-exit-rotate:-60turn;} 485 | .spin-out--70deg{--una-exit-rotate:-70deg;} 486 | .spin-out--70grad{--una-exit-rotate:-70grad;} 487 | .spin-out--70rad{--una-exit-rotate:-70rad;} 488 | .spin-out--70turn{--una-exit-rotate:-70turn;} 489 | .spin-out--80deg{--una-exit-rotate:-80deg;} 490 | .spin-out--80grad{--una-exit-rotate:-80grad;} 491 | .spin-out--80rad{--una-exit-rotate:-80rad;} 492 | .spin-out--80turn{--una-exit-rotate:-80turn;} 493 | .spin-out--90deg{--una-exit-rotate:-90deg;} 494 | .spin-out--90grad{--una-exit-rotate:-90grad;} 495 | .spin-out--90rad{--una-exit-rotate:-90rad;} 496 | .spin-out--90turn{--una-exit-rotate:-90turn;} 497 | .spin-out-100deg{--una-exit-rotate:100deg;} 498 | .spin-out-100grad{--una-exit-rotate:100grad;} 499 | .spin-out-100rad{--una-exit-rotate:100rad;} 500 | .spin-out-100turn{--una-exit-rotate:100turn;} 501 | .spin-out-10deg{--una-exit-rotate:10deg;} 502 | .spin-out-10grad{--una-exit-rotate:10grad;} 503 | .spin-out-10rad{--una-exit-rotate:10rad;} 504 | .spin-out-10turn{--una-exit-rotate:10turn;} 505 | .spin-out-110deg{--una-exit-rotate:110deg;} 506 | .spin-out-110grad{--una-exit-rotate:110grad;} 507 | .spin-out-110rad{--una-exit-rotate:110rad;} 508 | .spin-out-110turn{--una-exit-rotate:110turn;} 509 | .spin-out-120deg{--una-exit-rotate:120deg;} 510 | .spin-out-120grad{--una-exit-rotate:120grad;} 511 | .spin-out-120rad{--una-exit-rotate:120rad;} 512 | .spin-out-120turn{--una-exit-rotate:120turn;} 513 | .spin-out-130deg{--una-exit-rotate:130deg;} 514 | .spin-out-130grad{--una-exit-rotate:130grad;} 515 | .spin-out-130rad{--una-exit-rotate:130rad;} 516 | .spin-out-130turn{--una-exit-rotate:130turn;} 517 | .spin-out-140deg{--una-exit-rotate:140deg;} 518 | .spin-out-140grad{--una-exit-rotate:140grad;} 519 | .spin-out-140rad{--una-exit-rotate:140rad;} 520 | .spin-out-140turn{--una-exit-rotate:140turn;} 521 | .spin-out-150deg{--una-exit-rotate:150deg;} 522 | .spin-out-150grad{--una-exit-rotate:150grad;} 523 | .spin-out-150rad{--una-exit-rotate:150rad;} 524 | .spin-out-150turn{--una-exit-rotate:150turn;} 525 | .spin-out-160deg{--una-exit-rotate:160deg;} 526 | .spin-out-160grad{--una-exit-rotate:160grad;} 527 | .spin-out-160rad{--una-exit-rotate:160rad;} 528 | .spin-out-160turn{--una-exit-rotate:160turn;} 529 | .spin-out-170deg{--una-exit-rotate:170deg;} 530 | .spin-out-170grad{--una-exit-rotate:170grad;} 531 | .spin-out-170rad{--una-exit-rotate:170rad;} 532 | .spin-out-170turn{--una-exit-rotate:170turn;} 533 | .spin-out-180deg{--una-exit-rotate:180deg;} 534 | .spin-out-180grad{--una-exit-rotate:180grad;} 535 | .spin-out-180rad{--una-exit-rotate:180rad;} 536 | .spin-out-180turn{--una-exit-rotate:180turn;} 537 | .spin-out-190deg{--una-exit-rotate:190deg;} 538 | .spin-out-190grad{--una-exit-rotate:190grad;} 539 | .spin-out-190rad{--una-exit-rotate:190rad;} 540 | .spin-out-190turn{--una-exit-rotate:190turn;} 541 | .spin-out-200deg{--una-exit-rotate:200deg;} 542 | .spin-out-200grad{--una-exit-rotate:200grad;} 543 | .spin-out-200rad{--una-exit-rotate:200rad;} 544 | .spin-out-200turn{--una-exit-rotate:200turn;} 545 | .spin-out-20deg{--una-exit-rotate:20deg;} 546 | .spin-out-20grad{--una-exit-rotate:20grad;} 547 | .spin-out-20rad{--una-exit-rotate:20rad;} 548 | .spin-out-20turn{--una-exit-rotate:20turn;} 549 | .spin-out-30deg{--una-exit-rotate:30deg;} 550 | .spin-out-30grad{--una-exit-rotate:30grad;} 551 | .spin-out-30rad{--una-exit-rotate:30rad;} 552 | .spin-out-30turn{--una-exit-rotate:30turn;} 553 | .spin-out-40deg{--una-exit-rotate:40deg;} 554 | .spin-out-40grad{--una-exit-rotate:40grad;} 555 | .spin-out-40rad{--una-exit-rotate:40rad;} 556 | .spin-out-40turn{--una-exit-rotate:40turn;} 557 | .spin-out-50deg{--una-exit-rotate:50deg;} 558 | .spin-out-50grad{--una-exit-rotate:50grad;} 559 | .spin-out-50rad{--una-exit-rotate:50rad;} 560 | .spin-out-50turn{--una-exit-rotate:50turn;} 561 | .spin-out-60deg{--una-exit-rotate:60deg;} 562 | .spin-out-60grad{--una-exit-rotate:60grad;} 563 | .spin-out-60rad{--una-exit-rotate:60rad;} 564 | .spin-out-60turn{--una-exit-rotate:60turn;} 565 | .spin-out-70deg{--una-exit-rotate:70deg;} 566 | .spin-out-70grad{--una-exit-rotate:70grad;} 567 | .spin-out-70rad{--una-exit-rotate:70rad;} 568 | .spin-out-70turn{--una-exit-rotate:70turn;} 569 | .spin-out-80deg{--una-exit-rotate:80deg;} 570 | .spin-out-80grad{--una-exit-rotate:80grad;} 571 | .spin-out-80rad{--una-exit-rotate:80rad;} 572 | .spin-out-80turn{--una-exit-rotate:80turn;} 573 | .spin-out-90deg{--una-exit-rotate:90deg;} 574 | .spin-out-90grad{--una-exit-rotate:90grad;} 575 | .spin-out-90rad{--una-exit-rotate:90rad;} 576 | .spin-out-90turn{--una-exit-rotate:90turn;}" 577 | `) 578 | }) 579 | 580 | 581 | it(`should not use any unit for "0"`, async ({ expect }) => { 582 | const classnames = [ 583 | 'spin-out-0', 584 | 'spin-out-0deg', 585 | 'spin-out-0rad', 586 | 'spin-out-0grad', 587 | 'spin-out-0turn', 588 | ] 589 | 590 | const { matched, css } = await uno.generate(classnames) 591 | 592 | expect(matched).toStrictEqual(new Set(classnames)) 593 | expect(css).toMatchInlineSnapshot(` 594 | "/* layer: default */ 595 | .spin-out-0, 596 | .spin-out-0deg, 597 | .spin-out-0grad, 598 | .spin-out-0rad, 599 | .spin-out-0turn{--una-exit-rotate:0deg;}" 600 | `) 601 | }) 602 | }) 603 | 604 | 605 | describe('css variable', () => { 606 | it(`should handle css variables`, async ({ expect }) => { 607 | const classnames = CSS_VARIABLES.map(i => `spin-out-${i}`) 608 | 609 | const { matched, css } = await uno.generate(classnames.join(' ')) 610 | 611 | expect(matched).toStrictEqual(new Set(classnames)) 612 | expect(css).toMatchInlineSnapshot(` 613 | "/* layer: default */ 614 | .spin-out-\\$foo{--una-exit-rotate:var(--foo);} 615 | .spin-out-\\$foo-bar{--una-exit-rotate:var(--foo-bar);} 616 | .spin-out-\\$fooBar{--una-exit-rotate:var(--fooBar);}" 617 | `) 618 | }) 619 | }) 620 | }) 621 | }) 622 | -------------------------------------------------------------------------------- /test/slide.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { CSS_VARIABLE_PREFIX } from '@/constants' 3 | import { CSS_VARIABLES, DECIMALS, FRACTIONS, INTEGERS } from '~/data' 4 | import { uno } from '~/utils' 5 | 6 | 7 | describe.concurrent('slide animation', () => { 8 | describe('slide-in', () => { 9 | describe('misc', () => { 10 | it(`"should generate "${CSS_VARIABLE_PREFIX}-enter-translate-x" and "-y" css variable and default to "100%"`, async ({ expect }) => { 11 | const classnames = [ 12 | 'slide-in-t', 13 | 'slide-in-b', 14 | 'slide-in-l', 15 | 'slide-in-r', 16 | ] 17 | 18 | const { matched, css } = await uno.generate(classnames.join(' ')) 19 | 20 | expect(matched).toStrictEqual(new Set(classnames)) 21 | expect(css).toMatchInlineSnapshot(` 22 | "/* layer: default */ 23 | .slide-in-b{--una-enter-translate-y:100%;} 24 | .slide-in-l{--una-enter-translate-x:-100%;} 25 | .slide-in-r{--una-enter-translate-x:100%;} 26 | .slide-in-t{--una-enter-translate-y:-100%;}" 27 | `) 28 | }) 29 | 30 | 31 | it(`should handle both with or without "-from"`, async ({ expect }) => { 32 | const classnames = [ 33 | 'slide-in-t', 34 | 'slide-in-b', 35 | 'slide-in-l', 36 | 'slide-in-r', 37 | 'slide-in-from-t', 38 | 'slide-in-from-b', 39 | 'slide-in-from-l', 40 | 'slide-in-from-r', 41 | ] 42 | 43 | const { matched, css } = await uno.generate(classnames.join(' ')) 44 | 45 | expect(matched).toStrictEqual(new Set(classnames)) 46 | expect(css).toMatchInlineSnapshot(` 47 | "/* layer: default */ 48 | .slide-in-b, 49 | .slide-in-from-b{--una-enter-translate-y:100%;} 50 | .slide-in-from-l, 51 | .slide-in-l{--una-enter-translate-x:-100%;} 52 | .slide-in-from-r, 53 | .slide-in-r{--una-enter-translate-x:100%;} 54 | .slide-in-from-t, 55 | .slide-in-t{--una-enter-translate-y:-100%;}" 56 | `) 57 | }) 58 | 59 | 60 | it(`should alias "t|b|l|r" to "top|bottom|left|right"`, async ({ expect }) => { 61 | const classnames = [ 62 | 'slide-in-t', 63 | 'slide-in-b', 64 | 'slide-in-l', 65 | 'slide-in-r', 66 | 'slide-in-top', 67 | 'slide-in-bottom', 68 | 'slide-in-left', 69 | 'slide-in-right', 70 | ] 71 | 72 | const { matched, css } = await uno.generate(classnames.join(' ')) 73 | 74 | expect(matched).toStrictEqual(new Set(classnames)) 75 | expect(css).toMatchInlineSnapshot(` 76 | "/* layer: default */ 77 | .slide-in-b, 78 | .slide-in-bottom{--una-enter-translate-y:100%;} 79 | .slide-in-l, 80 | .slide-in-left{--una-enter-translate-x:-100%;} 81 | .slide-in-r, 82 | .slide-in-right{--una-enter-translate-x:100%;} 83 | .slide-in-t, 84 | .slide-in-top{--una-enter-translate-y:-100%;}" 85 | `) 86 | }) 87 | }) 88 | 89 | 90 | describe('direction', () => { 91 | it(`should generate "top|bottom" as "translate-y"`, async ({ expect }) => { 92 | const classnames = [ 93 | 'slide-in-t', 94 | 'slide-in-b', 95 | ] 96 | 97 | const { matched, css } = await uno.generate(classnames.join(' ')) 98 | 99 | expect(matched).toStrictEqual(new Set(classnames)) 100 | expect(css).toMatchInlineSnapshot(` 101 | "/* layer: default */ 102 | .slide-in-b{--una-enter-translate-y:100%;} 103 | .slide-in-t{--una-enter-translate-y:-100%;}" 104 | `) 105 | }) 106 | 107 | 108 | it(`should generate "left|right" as "translate-x"`, async ({ expect }) => { 109 | const classnames = [ 110 | 'slide-in-l', 111 | 'slide-in-r', 112 | ] 113 | 114 | const { matched, css } = await uno.generate(classnames.join(' ')) 115 | 116 | expect(matched).toStrictEqual(new Set(classnames)) 117 | expect(css).toMatchInlineSnapshot(` 118 | "/* layer: default */ 119 | .slide-in-l{--una-enter-translate-x:-100%;} 120 | .slide-in-r{--una-enter-translate-x:100%;}" 121 | `) 122 | }) 123 | }) 124 | 125 | 126 | describe('positivity and negativity', () => { 127 | it(`should generate negative value for "top|left"`, async ({ expect }) => { 128 | const classnames = [ 129 | 'slide-in-t', 130 | 'slide-in-l', 131 | ] 132 | 133 | const { matched, css } = await uno.generate(classnames.join(' ')) 134 | 135 | expect(matched).toStrictEqual(new Set(classnames)) 136 | expect(css).toMatchInlineSnapshot(` 137 | "/* layer: default */ 138 | .slide-in-l{--una-enter-translate-x:-100%;} 139 | .slide-in-t{--una-enter-translate-y:-100%;}" 140 | `) 141 | }) 142 | 143 | 144 | it(`should generate positive value for "bottom|right"`, async ({ expect }) => { 145 | const classnames = [ 146 | 'slide-in-b', 147 | 'slide-in-r', 148 | ] 149 | 150 | const { matched, css } = await uno.generate(classnames.join(' ')) 151 | 152 | expect(matched).toStrictEqual(new Set(classnames)) 153 | expect(css).toMatchInlineSnapshot(` 154 | "/* layer: default */ 155 | .slide-in-b{--una-enter-translate-y:100%;} 156 | .slide-in-r{--una-enter-translate-x:100%;}" 157 | `) 158 | }) 159 | 160 | 161 | it(`should convert negative value to positive for "top|left"`, async ({ expect }) => { 162 | const classnames = [ 163 | 'slide-in-t--10', 164 | 'slide-in-l--10', 165 | ] 166 | 167 | const { matched, css } = await uno.generate(classnames.join(' ')) 168 | 169 | expect(matched).toStrictEqual(new Set(classnames)) 170 | expect(css).toMatchInlineSnapshot(` 171 | "/* layer: default */ 172 | .slide-in-l--10{--una-enter-translate-x:2.5rem;} 173 | .slide-in-t--10{--una-enter-translate-y:2.5rem;}" 174 | `) 175 | }) 176 | }) 177 | 178 | 179 | describe('rem', () => { 180 | it(`should convert any numbers to "rem" (x / 4rem) including negative`, async ({ expect }) => { 181 | const classnames = INTEGERS.map(i => `slide-in-t-${i}`) 182 | 183 | const { matched, css } = await uno.generate(classnames.join(' ')) 184 | 185 | expect(matched).toStrictEqual(new Set(classnames)) 186 | expect(css).toMatchInlineSnapshot(` 187 | "/* layer: default */ 188 | .slide-in-t--10{--una-enter-translate-y:2.5rem;} 189 | .slide-in-t--100{--una-enter-translate-y:25rem;} 190 | .slide-in-t--110{--una-enter-translate-y:27.5rem;} 191 | .slide-in-t--120{--una-enter-translate-y:30rem;} 192 | .slide-in-t--130{--una-enter-translate-y:32.5rem;} 193 | .slide-in-t--140{--una-enter-translate-y:35rem;} 194 | .slide-in-t--150{--una-enter-translate-y:37.5rem;} 195 | .slide-in-t--160{--una-enter-translate-y:40rem;} 196 | .slide-in-t--170{--una-enter-translate-y:42.5rem;} 197 | .slide-in-t--180{--una-enter-translate-y:45rem;} 198 | .slide-in-t--190{--una-enter-translate-y:47.5rem;} 199 | .slide-in-t--20{--una-enter-translate-y:5rem;} 200 | .slide-in-t--200{--una-enter-translate-y:50rem;} 201 | .slide-in-t--30{--una-enter-translate-y:7.5rem;} 202 | .slide-in-t--40{--una-enter-translate-y:10rem;} 203 | .slide-in-t--50{--una-enter-translate-y:12.5rem;} 204 | .slide-in-t--60{--una-enter-translate-y:15rem;} 205 | .slide-in-t--70{--una-enter-translate-y:17.5rem;} 206 | .slide-in-t--80{--una-enter-translate-y:20rem;} 207 | .slide-in-t--90{--una-enter-translate-y:22.5rem;} 208 | .slide-in-t-0{--una-enter-translate-y:0;} 209 | .slide-in-t-10{--una-enter-translate-y:-2.5rem;} 210 | .slide-in-t-100{--una-enter-translate-y:-25rem;} 211 | .slide-in-t-110{--una-enter-translate-y:-27.5rem;} 212 | .slide-in-t-120{--una-enter-translate-y:-30rem;} 213 | .slide-in-t-130{--una-enter-translate-y:-32.5rem;} 214 | .slide-in-t-140{--una-enter-translate-y:-35rem;} 215 | .slide-in-t-150{--una-enter-translate-y:-37.5rem;} 216 | .slide-in-t-160{--una-enter-translate-y:-40rem;} 217 | .slide-in-t-170{--una-enter-translate-y:-42.5rem;} 218 | .slide-in-t-180{--una-enter-translate-y:-45rem;} 219 | .slide-in-t-190{--una-enter-translate-y:-47.5rem;} 220 | .slide-in-t-20{--una-enter-translate-y:-5rem;} 221 | .slide-in-t-200{--una-enter-translate-y:-50rem;} 222 | .slide-in-t-30{--una-enter-translate-y:-7.5rem;} 223 | .slide-in-t-40{--una-enter-translate-y:-10rem;} 224 | .slide-in-t-50{--una-enter-translate-y:-12.5rem;} 225 | .slide-in-t-60{--una-enter-translate-y:-15rem;} 226 | .slide-in-t-70{--una-enter-translate-y:-17.5rem;} 227 | .slide-in-t-80{--una-enter-translate-y:-20rem;} 228 | .slide-in-t-90{--una-enter-translate-y:-22.5rem;}" 229 | `) 230 | }) 231 | 232 | 233 | it(`should also convert decimals including negative`, async ({ expect }) => { 234 | const classnames = DECIMALS.map(i => `slide-in-t-${i}`) 235 | 236 | const { matched, css } = await uno.generate(classnames.join(' ')) 237 | 238 | expect(matched).toStrictEqual(new Set(classnames)) 239 | expect(css).toMatchInlineSnapshot(` 240 | "/* layer: default */ 241 | .slide-in-t--0\\.1{--una-enter-translate-y:0.025rem;} 242 | .slide-in-t--10\\.1{--una-enter-translate-y:2.525rem;} 243 | .slide-in-t--180\\.37{--una-enter-translate-y:45.0925rem;} 244 | .slide-in-t--199\\.9{--una-enter-translate-y:49.975rem;} 245 | .slide-in-t--52\\.1{--una-enter-translate-y:13.025rem;} 246 | .slide-in-t--66\\.66{--una-enter-translate-y:16.665rem;} 247 | .slide-in-t-0\\.1{--una-enter-translate-y:-0.025rem;} 248 | .slide-in-t-10\\.1{--una-enter-translate-y:-2.525rem;} 249 | .slide-in-t-180\\.37{--una-enter-translate-y:-45.0925rem;} 250 | .slide-in-t-199\\.9{--una-enter-translate-y:-49.975rem;} 251 | .slide-in-t-52\\.1{--una-enter-translate-y:-13.025rem;} 252 | .slide-in-t-66\\.66{--una-enter-translate-y:-16.665rem;} 253 | .slide-in-t-99\\.9{--una-enter-translate-y:-24.975rem;}" 254 | `) 255 | }) 256 | }) 257 | 258 | 259 | describe('percentage', () => { 260 | it(`should leave any percentage with "%" symbol as is`, async ({ expect }) => { 261 | const classnames = [ 262 | ...INTEGERS.map(i => `slide-in-t-${i}%`), 263 | ...DECIMALS.map(i => `slide-in-t-${i}%`), 264 | ] 265 | 266 | const { matched, css } = await uno.generate(classnames.join(' ')) 267 | 268 | expect(matched).toStrictEqual(new Set(classnames)) 269 | expect(css).toMatchInlineSnapshot(` 270 | "/* layer: default */ 271 | .slide-in-t--0\\.1\\%{--una-enter-translate-y:0.1%;} 272 | .slide-in-t--10\\.1\\%{--una-enter-translate-y:10.1%;} 273 | .slide-in-t--10\\%{--una-enter-translate-y:10%;} 274 | .slide-in-t--100\\%{--una-enter-translate-y:100%;} 275 | .slide-in-t--110\\%{--una-enter-translate-y:110%;} 276 | .slide-in-t--120\\%{--una-enter-translate-y:120%;} 277 | .slide-in-t--130\\%{--una-enter-translate-y:130%;} 278 | .slide-in-t--140\\%{--una-enter-translate-y:140%;} 279 | .slide-in-t--150\\%{--una-enter-translate-y:150%;} 280 | .slide-in-t--160\\%{--una-enter-translate-y:160%;} 281 | .slide-in-t--170\\%{--una-enter-translate-y:170%;} 282 | .slide-in-t--180\\.37\\%{--una-enter-translate-y:180.37%;} 283 | .slide-in-t--180\\%{--una-enter-translate-y:180%;} 284 | .slide-in-t--190\\%{--una-enter-translate-y:190%;} 285 | .slide-in-t--199\\.9\\%{--una-enter-translate-y:199.9%;} 286 | .slide-in-t--20\\%{--una-enter-translate-y:20%;} 287 | .slide-in-t--200\\%{--una-enter-translate-y:200%;} 288 | .slide-in-t--30\\%{--una-enter-translate-y:30%;} 289 | .slide-in-t--40\\%{--una-enter-translate-y:40%;} 290 | .slide-in-t--50\\%{--una-enter-translate-y:50%;} 291 | .slide-in-t--52\\.1\\%{--una-enter-translate-y:52.1%;} 292 | .slide-in-t--60\\%{--una-enter-translate-y:60%;} 293 | .slide-in-t--66\\.66\\%{--una-enter-translate-y:66.66%;} 294 | .slide-in-t--70\\%{--una-enter-translate-y:70%;} 295 | .slide-in-t--80\\%{--una-enter-translate-y:80%;} 296 | .slide-in-t--90\\%{--una-enter-translate-y:90%;} 297 | .slide-in-t-0\\.1\\%{--una-enter-translate-y:-0.1%;} 298 | .slide-in-t-0\\%{--una-enter-translate-y:0;} 299 | .slide-in-t-10\\.1\\%{--una-enter-translate-y:-10.1%;} 300 | .slide-in-t-10\\%{--una-enter-translate-y:-10%;} 301 | .slide-in-t-100\\%{--una-enter-translate-y:-100%;} 302 | .slide-in-t-110\\%{--una-enter-translate-y:-110%;} 303 | .slide-in-t-120\\%{--una-enter-translate-y:-120%;} 304 | .slide-in-t-130\\%{--una-enter-translate-y:-130%;} 305 | .slide-in-t-140\\%{--una-enter-translate-y:-140%;} 306 | .slide-in-t-150\\%{--una-enter-translate-y:-150%;} 307 | .slide-in-t-160\\%{--una-enter-translate-y:-160%;} 308 | .slide-in-t-170\\%{--una-enter-translate-y:-170%;} 309 | .slide-in-t-180\\.37\\%{--una-enter-translate-y:-180.37%;} 310 | .slide-in-t-180\\%{--una-enter-translate-y:-180%;} 311 | .slide-in-t-190\\%{--una-enter-translate-y:-190%;} 312 | .slide-in-t-199\\.9\\%{--una-enter-translate-y:-199.9%;} 313 | .slide-in-t-20\\%{--una-enter-translate-y:-20%;} 314 | .slide-in-t-200\\%{--una-enter-translate-y:-200%;} 315 | .slide-in-t-30\\%{--una-enter-translate-y:-30%;} 316 | .slide-in-t-40\\%{--una-enter-translate-y:-40%;} 317 | .slide-in-t-50\\%{--una-enter-translate-y:-50%;} 318 | .slide-in-t-52\\.1\\%{--una-enter-translate-y:-52.1%;} 319 | .slide-in-t-60\\%{--una-enter-translate-y:-60%;} 320 | .slide-in-t-66\\.66\\%{--una-enter-translate-y:-66.66%;} 321 | .slide-in-t-70\\%{--una-enter-translate-y:-70%;} 322 | .slide-in-t-80\\%{--una-enter-translate-y:-80%;} 323 | .slide-in-t-90\\%{--una-enter-translate-y:-90%;} 324 | .slide-in-t-99\\.9\\%{--una-enter-translate-y:-99.9%;}" 325 | `) 326 | }) 327 | }) 328 | 329 | 330 | describe('fraction', () => { 331 | it(`should convert any fractions including negative`, async ({ expect }) => { 332 | const classnames = FRACTIONS.map(i => `slide-in-t-${i}`) 333 | 334 | const { matched, css } = await uno.generate(classnames.join(' ')) 335 | 336 | expect(matched).toStrictEqual(new Set(classnames)) 337 | expect(css).toMatchInlineSnapshot(` 338 | "/* layer: default */ 339 | .slide-in-t--1\\/3{--una-enter-translate-y:33.3333333333%;} 340 | .slide-in-t--1\\/4{--una-enter-translate-y:25%;} 341 | .slide-in-t--1\\/6{--una-enter-translate-y:16.6666666667%;} 342 | .slide-in-t--2\\/3{--una-enter-translate-y:66.6666666667%;} 343 | .slide-in-t--3\\/4{--una-enter-translate-y:75%;} 344 | .slide-in-t--5\\/6{--una-enter-translate-y:83.3333333333%;} 345 | .slide-in-t-1\\/3{--una-enter-translate-y:-33.3333333333%;} 346 | .slide-in-t-1\\/4{--una-enter-translate-y:-25%;} 347 | .slide-in-t-1\\/6{--una-enter-translate-y:-16.6666666667%;} 348 | .slide-in-t-2\\/3{--una-enter-translate-y:-66.6666666667%;} 349 | .slide-in-t-3\\/4{--una-enter-translate-y:-75%;} 350 | .slide-in-t-5\\/6{--una-enter-translate-y:-83.3333333333%;}" 351 | `) 352 | }) 353 | 354 | 355 | it(`should convert "full" to "100%`, async ({ expect }) => { 356 | const { css } = await uno.generate('slide-in-t-full') 357 | 358 | expect(css).toMatchInlineSnapshot(` 359 | "/* layer: default */ 360 | .slide-in-t-full{--una-enter-translate-y:-100%;}" 361 | `) 362 | }) 363 | }) 364 | 365 | 366 | describe('css variable', () => { 367 | it(`should handle css variables`, async ({ expect }) => { 368 | const classnames = CSS_VARIABLES.map(i => `slide-in-t-${i}`) 369 | 370 | const { matched, css } = await uno.generate(classnames.join(' ')) 371 | 372 | expect(matched).toStrictEqual(new Set(classnames)) 373 | expect(css).toMatchInlineSnapshot(` 374 | "/* layer: default */ 375 | .slide-in-t-\\$foo{--una-enter-translate-y:var(--foo);} 376 | .slide-in-t-\\$foo-bar{--una-enter-translate-y:var(--foo-bar);} 377 | .slide-in-t-\\$fooBar{--una-enter-translate-y:var(--fooBar);}" 378 | `) 379 | }) 380 | }) 381 | }) 382 | 383 | 384 | describe('slide-out', () => { 385 | describe('misc', () => { 386 | it(`"should generate "${CSS_VARIABLE_PREFIX}-exit-translate-x" and "-y" css variable and default to "100%"`, async ({ expect }) => { 387 | const classnames = [ 388 | 'slide-out-t', 389 | 'slide-out-b', 390 | 'slide-out-l', 391 | 'slide-out-r', 392 | ] 393 | 394 | const { matched, css } = await uno.generate(classnames.join(' ')) 395 | 396 | expect(matched).toStrictEqual(new Set(classnames)) 397 | expect(css).toMatchInlineSnapshot(` 398 | "/* layer: default */ 399 | .slide-out-b{--una-exit-translate-y:100%;} 400 | .slide-out-l{--una-exit-translate-x:-100%;} 401 | .slide-out-r{--una-exit-translate-x:100%;} 402 | .slide-out-t{--una-exit-translate-y:-100%;}" 403 | `) 404 | }) 405 | 406 | 407 | it(`should handle both with or without "-to"`, async ({ expect }) => { 408 | const classnames = [ 409 | 'slide-out-t', 410 | 'slide-out-b', 411 | 'slide-out-l', 412 | 'slide-out-r', 413 | 'slide-out-to-t', 414 | 'slide-out-to-b', 415 | 'slide-out-to-l', 416 | 'slide-out-to-r', 417 | ] 418 | 419 | const { matched, css } = await uno.generate(classnames.join(' ')) 420 | 421 | expect(matched).toStrictEqual(new Set(classnames)) 422 | expect(css).toMatchInlineSnapshot(` 423 | "/* layer: default */ 424 | .slide-out-b, 425 | .slide-out-to-b{--una-exit-translate-y:100%;} 426 | .slide-out-l, 427 | .slide-out-to-l{--una-exit-translate-x:-100%;} 428 | .slide-out-r, 429 | .slide-out-to-r{--una-exit-translate-x:100%;} 430 | .slide-out-t, 431 | .slide-out-to-t{--una-exit-translate-y:-100%;}" 432 | `) 433 | }) 434 | 435 | 436 | it(`should alias "t|b|l|r" to "top|bottom|left|right"`, async ({ expect }) => { 437 | const classnames = [ 438 | 'slide-out-t', 439 | 'slide-out-b', 440 | 'slide-out-l', 441 | 'slide-out-r', 442 | 'slide-out-top', 443 | 'slide-out-bottom', 444 | 'slide-out-left', 445 | 'slide-out-right', 446 | ] 447 | 448 | const { matched, css } = await uno.generate(classnames.join(' ')) 449 | 450 | expect(matched).toStrictEqual(new Set(classnames)) 451 | expect(css).toMatchInlineSnapshot(` 452 | "/* layer: default */ 453 | .slide-out-b, 454 | .slide-out-bottom{--una-exit-translate-y:100%;} 455 | .slide-out-l, 456 | .slide-out-left{--una-exit-translate-x:-100%;} 457 | .slide-out-r, 458 | .slide-out-right{--una-exit-translate-x:100%;} 459 | .slide-out-t, 460 | .slide-out-top{--una-exit-translate-y:-100%;}" 461 | `) 462 | }) 463 | }) 464 | 465 | 466 | describe('direction', () => { 467 | it(`should generate "top|bottom" as "translate-y"`, async ({ expect }) => { 468 | const classnames = [ 469 | 'slide-out-t', 470 | 'slide-out-b', 471 | ] 472 | 473 | const { matched, css } = await uno.generate(classnames.join(' ')) 474 | 475 | expect(matched).toStrictEqual(new Set(classnames)) 476 | expect(css).toMatchInlineSnapshot(` 477 | "/* layer: default */ 478 | .slide-out-b{--una-exit-translate-y:100%;} 479 | .slide-out-t{--una-exit-translate-y:-100%;}" 480 | `) 481 | }) 482 | 483 | 484 | it(`should generate "left|right" as "translate-x"`, async ({ expect }) => { 485 | const classnames = [ 486 | 'slide-out-l', 487 | 'slide-out-r', 488 | ] 489 | 490 | const { matched, css } = await uno.generate(classnames.join(' ')) 491 | 492 | expect(matched).toStrictEqual(new Set(classnames)) 493 | expect(css).toMatchInlineSnapshot(` 494 | "/* layer: default */ 495 | .slide-out-l{--una-exit-translate-x:-100%;} 496 | .slide-out-r{--una-exit-translate-x:100%;}" 497 | `) 498 | }) 499 | }) 500 | 501 | 502 | describe('positivity and negativity', () => { 503 | it(`should generate negative value for "top|left"`, async ({ expect }) => { 504 | const classnames = [ 505 | 'slide-out-t', 506 | 'slide-out-l', 507 | ] 508 | 509 | const { matched, css } = await uno.generate(classnames.join(' ')) 510 | 511 | expect(matched).toStrictEqual(new Set(classnames)) 512 | expect(css).toMatchInlineSnapshot(` 513 | "/* layer: default */ 514 | .slide-out-l{--una-exit-translate-x:-100%;} 515 | .slide-out-t{--una-exit-translate-y:-100%;}" 516 | `) 517 | }) 518 | 519 | 520 | it(`should generate positive value for "bottom|right"`, async ({ expect }) => { 521 | const classnames = [ 522 | 'slide-out-b', 523 | 'slide-out-r', 524 | ] 525 | 526 | const { matched, css } = await uno.generate(classnames.join(' ')) 527 | 528 | expect(matched).toStrictEqual(new Set(classnames)) 529 | expect(css).toMatchInlineSnapshot(` 530 | "/* layer: default */ 531 | .slide-out-b{--una-exit-translate-y:100%;} 532 | .slide-out-r{--una-exit-translate-x:100%;}" 533 | `) 534 | }) 535 | 536 | 537 | it(`should convert negative value to positive for "top|left"`, async ({ expect }) => { 538 | const classnames = [ 539 | 'slide-out-t--10', 540 | 'slide-out-l--10', 541 | ] 542 | 543 | const { matched, css } = await uno.generate(classnames.join(' ')) 544 | 545 | expect(matched).toStrictEqual(new Set(classnames)) 546 | expect(css).toMatchInlineSnapshot(` 547 | "/* layer: default */ 548 | .slide-out-l--10{--una-exit-translate-x:2.5rem;} 549 | .slide-out-t--10{--una-exit-translate-y:2.5rem;}" 550 | `) 551 | }) 552 | }) 553 | 554 | 555 | describe('rem', () => { 556 | it(`should convert any numbers to "rem" (x / 4rem) including negative`, async ({ expect }) => { 557 | const classnames = INTEGERS.map(i => `slide-out-t-${i}`) 558 | 559 | const { matched, css } = await uno.generate(classnames.join(' ')) 560 | 561 | expect(matched).toStrictEqual(new Set(classnames)) 562 | expect(css).toMatchInlineSnapshot(` 563 | "/* layer: default */ 564 | .slide-out-t--10{--una-exit-translate-y:2.5rem;} 565 | .slide-out-t--100{--una-exit-translate-y:25rem;} 566 | .slide-out-t--110{--una-exit-translate-y:27.5rem;} 567 | .slide-out-t--120{--una-exit-translate-y:30rem;} 568 | .slide-out-t--130{--una-exit-translate-y:32.5rem;} 569 | .slide-out-t--140{--una-exit-translate-y:35rem;} 570 | .slide-out-t--150{--una-exit-translate-y:37.5rem;} 571 | .slide-out-t--160{--una-exit-translate-y:40rem;} 572 | .slide-out-t--170{--una-exit-translate-y:42.5rem;} 573 | .slide-out-t--180{--una-exit-translate-y:45rem;} 574 | .slide-out-t--190{--una-exit-translate-y:47.5rem;} 575 | .slide-out-t--20{--una-exit-translate-y:5rem;} 576 | .slide-out-t--200{--una-exit-translate-y:50rem;} 577 | .slide-out-t--30{--una-exit-translate-y:7.5rem;} 578 | .slide-out-t--40{--una-exit-translate-y:10rem;} 579 | .slide-out-t--50{--una-exit-translate-y:12.5rem;} 580 | .slide-out-t--60{--una-exit-translate-y:15rem;} 581 | .slide-out-t--70{--una-exit-translate-y:17.5rem;} 582 | .slide-out-t--80{--una-exit-translate-y:20rem;} 583 | .slide-out-t--90{--una-exit-translate-y:22.5rem;} 584 | .slide-out-t-0{--una-exit-translate-y:0;} 585 | .slide-out-t-10{--una-exit-translate-y:-2.5rem;} 586 | .slide-out-t-100{--una-exit-translate-y:-25rem;} 587 | .slide-out-t-110{--una-exit-translate-y:-27.5rem;} 588 | .slide-out-t-120{--una-exit-translate-y:-30rem;} 589 | .slide-out-t-130{--una-exit-translate-y:-32.5rem;} 590 | .slide-out-t-140{--una-exit-translate-y:-35rem;} 591 | .slide-out-t-150{--una-exit-translate-y:-37.5rem;} 592 | .slide-out-t-160{--una-exit-translate-y:-40rem;} 593 | .slide-out-t-170{--una-exit-translate-y:-42.5rem;} 594 | .slide-out-t-180{--una-exit-translate-y:-45rem;} 595 | .slide-out-t-190{--una-exit-translate-y:-47.5rem;} 596 | .slide-out-t-20{--una-exit-translate-y:-5rem;} 597 | .slide-out-t-200{--una-exit-translate-y:-50rem;} 598 | .slide-out-t-30{--una-exit-translate-y:-7.5rem;} 599 | .slide-out-t-40{--una-exit-translate-y:-10rem;} 600 | .slide-out-t-50{--una-exit-translate-y:-12.5rem;} 601 | .slide-out-t-60{--una-exit-translate-y:-15rem;} 602 | .slide-out-t-70{--una-exit-translate-y:-17.5rem;} 603 | .slide-out-t-80{--una-exit-translate-y:-20rem;} 604 | .slide-out-t-90{--una-exit-translate-y:-22.5rem;}" 605 | `) 606 | }) 607 | 608 | 609 | it(`should also convert decimals including negative`, async ({ expect }) => { 610 | const classnames = DECIMALS.map(i => `slide-out-t-${i}`) 611 | 612 | const { matched, css } = await uno.generate(classnames.join(' ')) 613 | 614 | expect(matched).toStrictEqual(new Set(classnames)) 615 | expect(css).toMatchInlineSnapshot(` 616 | "/* layer: default */ 617 | .slide-out-t--0\\.1{--una-exit-translate-y:0.025rem;} 618 | .slide-out-t--10\\.1{--una-exit-translate-y:2.525rem;} 619 | .slide-out-t--180\\.37{--una-exit-translate-y:45.0925rem;} 620 | .slide-out-t--199\\.9{--una-exit-translate-y:49.975rem;} 621 | .slide-out-t--52\\.1{--una-exit-translate-y:13.025rem;} 622 | .slide-out-t--66\\.66{--una-exit-translate-y:16.665rem;} 623 | .slide-out-t-0\\.1{--una-exit-translate-y:-0.025rem;} 624 | .slide-out-t-10\\.1{--una-exit-translate-y:-2.525rem;} 625 | .slide-out-t-180\\.37{--una-exit-translate-y:-45.0925rem;} 626 | .slide-out-t-199\\.9{--una-exit-translate-y:-49.975rem;} 627 | .slide-out-t-52\\.1{--una-exit-translate-y:-13.025rem;} 628 | .slide-out-t-66\\.66{--una-exit-translate-y:-16.665rem;} 629 | .slide-out-t-99\\.9{--una-exit-translate-y:-24.975rem;}" 630 | `) 631 | }) 632 | }) 633 | 634 | 635 | describe('percentage', () => { 636 | it(`should leave any percentage with "%" symbol as is`, async ({ expect }) => { 637 | const classnames = [ 638 | ...INTEGERS.map(i => `slide-out-t-${i}%`), 639 | ...DECIMALS.map(i => `slide-out-t-${i}%`), 640 | ] 641 | 642 | const { matched, css } = await uno.generate(classnames.join(' ')) 643 | 644 | expect(matched).toStrictEqual(new Set(classnames)) 645 | expect(css).toMatchInlineSnapshot(` 646 | "/* layer: default */ 647 | .slide-out-t--0\\.1\\%{--una-exit-translate-y:0.1%;} 648 | .slide-out-t--10\\.1\\%{--una-exit-translate-y:10.1%;} 649 | .slide-out-t--10\\%{--una-exit-translate-y:10%;} 650 | .slide-out-t--100\\%{--una-exit-translate-y:100%;} 651 | .slide-out-t--110\\%{--una-exit-translate-y:110%;} 652 | .slide-out-t--120\\%{--una-exit-translate-y:120%;} 653 | .slide-out-t--130\\%{--una-exit-translate-y:130%;} 654 | .slide-out-t--140\\%{--una-exit-translate-y:140%;} 655 | .slide-out-t--150\\%{--una-exit-translate-y:150%;} 656 | .slide-out-t--160\\%{--una-exit-translate-y:160%;} 657 | .slide-out-t--170\\%{--una-exit-translate-y:170%;} 658 | .slide-out-t--180\\.37\\%{--una-exit-translate-y:180.37%;} 659 | .slide-out-t--180\\%{--una-exit-translate-y:180%;} 660 | .slide-out-t--190\\%{--una-exit-translate-y:190%;} 661 | .slide-out-t--199\\.9\\%{--una-exit-translate-y:199.9%;} 662 | .slide-out-t--20\\%{--una-exit-translate-y:20%;} 663 | .slide-out-t--200\\%{--una-exit-translate-y:200%;} 664 | .slide-out-t--30\\%{--una-exit-translate-y:30%;} 665 | .slide-out-t--40\\%{--una-exit-translate-y:40%;} 666 | .slide-out-t--50\\%{--una-exit-translate-y:50%;} 667 | .slide-out-t--52\\.1\\%{--una-exit-translate-y:52.1%;} 668 | .slide-out-t--60\\%{--una-exit-translate-y:60%;} 669 | .slide-out-t--66\\.66\\%{--una-exit-translate-y:66.66%;} 670 | .slide-out-t--70\\%{--una-exit-translate-y:70%;} 671 | .slide-out-t--80\\%{--una-exit-translate-y:80%;} 672 | .slide-out-t--90\\%{--una-exit-translate-y:90%;} 673 | .slide-out-t-0\\.1\\%{--una-exit-translate-y:-0.1%;} 674 | .slide-out-t-0\\%{--una-exit-translate-y:0;} 675 | .slide-out-t-10\\.1\\%{--una-exit-translate-y:-10.1%;} 676 | .slide-out-t-10\\%{--una-exit-translate-y:-10%;} 677 | .slide-out-t-100\\%{--una-exit-translate-y:-100%;} 678 | .slide-out-t-110\\%{--una-exit-translate-y:-110%;} 679 | .slide-out-t-120\\%{--una-exit-translate-y:-120%;} 680 | .slide-out-t-130\\%{--una-exit-translate-y:-130%;} 681 | .slide-out-t-140\\%{--una-exit-translate-y:-140%;} 682 | .slide-out-t-150\\%{--una-exit-translate-y:-150%;} 683 | .slide-out-t-160\\%{--una-exit-translate-y:-160%;} 684 | .slide-out-t-170\\%{--una-exit-translate-y:-170%;} 685 | .slide-out-t-180\\.37\\%{--una-exit-translate-y:-180.37%;} 686 | .slide-out-t-180\\%{--una-exit-translate-y:-180%;} 687 | .slide-out-t-190\\%{--una-exit-translate-y:-190%;} 688 | .slide-out-t-199\\.9\\%{--una-exit-translate-y:-199.9%;} 689 | .slide-out-t-20\\%{--una-exit-translate-y:-20%;} 690 | .slide-out-t-200\\%{--una-exit-translate-y:-200%;} 691 | .slide-out-t-30\\%{--una-exit-translate-y:-30%;} 692 | .slide-out-t-40\\%{--una-exit-translate-y:-40%;} 693 | .slide-out-t-50\\%{--una-exit-translate-y:-50%;} 694 | .slide-out-t-52\\.1\\%{--una-exit-translate-y:-52.1%;} 695 | .slide-out-t-60\\%{--una-exit-translate-y:-60%;} 696 | .slide-out-t-66\\.66\\%{--una-exit-translate-y:-66.66%;} 697 | .slide-out-t-70\\%{--una-exit-translate-y:-70%;} 698 | .slide-out-t-80\\%{--una-exit-translate-y:-80%;} 699 | .slide-out-t-90\\%{--una-exit-translate-y:-90%;} 700 | .slide-out-t-99\\.9\\%{--una-exit-translate-y:-99.9%;}" 701 | `) 702 | }) 703 | }) 704 | 705 | 706 | describe('fraction', () => { 707 | it(`should convert any fractions including negative`, async ({ expect }) => { 708 | const classnames = FRACTIONS.map(i => `slide-out-t-${i}`) 709 | 710 | const { matched, css } = await uno.generate(classnames.join(' ')) 711 | 712 | expect(matched).toStrictEqual(new Set(classnames)) 713 | expect(css).toMatchInlineSnapshot(` 714 | "/* layer: default */ 715 | .slide-out-t--1\\/3{--una-exit-translate-y:33.3333333333%;} 716 | .slide-out-t--1\\/4{--una-exit-translate-y:25%;} 717 | .slide-out-t--1\\/6{--una-exit-translate-y:16.6666666667%;} 718 | .slide-out-t--2\\/3{--una-exit-translate-y:66.6666666667%;} 719 | .slide-out-t--3\\/4{--una-exit-translate-y:75%;} 720 | .slide-out-t--5\\/6{--una-exit-translate-y:83.3333333333%;} 721 | .slide-out-t-1\\/3{--una-exit-translate-y:-33.3333333333%;} 722 | .slide-out-t-1\\/4{--una-exit-translate-y:-25%;} 723 | .slide-out-t-1\\/6{--una-exit-translate-y:-16.6666666667%;} 724 | .slide-out-t-2\\/3{--una-exit-translate-y:-66.6666666667%;} 725 | .slide-out-t-3\\/4{--una-exit-translate-y:-75%;} 726 | .slide-out-t-5\\/6{--una-exit-translate-y:-83.3333333333%;}" 727 | `) 728 | }) 729 | 730 | 731 | it(`should convert "full" to "100%`, async ({ expect }) => { 732 | const { css } = await uno.generate('slide-out-t-full') 733 | 734 | expect(css).toMatchInlineSnapshot(` 735 | "/* layer: default */ 736 | .slide-out-t-full{--una-exit-translate-y:-100%;}" 737 | `) 738 | }) 739 | }) 740 | 741 | 742 | describe('css variable', () => { 743 | it(`should handle css variables`, async ({ expect }) => { 744 | const classnames = CSS_VARIABLES.map(i => `slide-out-t-${i}`) 745 | 746 | const { matched, css } = await uno.generate(classnames.join(' ')) 747 | 748 | expect(matched).toStrictEqual(new Set(classnames)) 749 | expect(css).toMatchInlineSnapshot(` 750 | "/* layer: default */ 751 | .slide-out-t-\\$foo{--una-exit-translate-y:var(--foo);} 752 | .slide-out-t-\\$foo-bar{--una-exit-translate-y:var(--foo-bar);} 753 | .slide-out-t-\\$fooBar{--una-exit-translate-y:var(--fooBar);}" 754 | `) 755 | }) 756 | }) 757 | }) 758 | }) 759 | --------------------------------------------------------------------------------