tailwind-animatecss-kit 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 sericpieap
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.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # tailwind-animatecss-kit
2
+
3
+ A configurable Animate.css integration for Tailwind CSS 3. It reads the installed Animate.css catalog at build time, emits only the animations you select, and namespaces generated keyframes to prevent collisions.
4
+
5
+ Global `duration`, `delay`, and `iterationCount` values are passed to `@jasperquinn/postcss-motion-helpers`, which generates the corresponding Tailwind base custom properties.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install tailwind-animatecss-kit animate.css
11
+ ```
12
+
13
+ ## Basic usage
14
+
15
+ ```js
16
+ // tailwind.config.js
17
+ module.exports = {
18
+ content: ['./src/**/*.{html,js,jsx,ts,tsx,vue,svelte}'],
19
+ plugins: [
20
+ require('tailwind-animatecss-kit')({
21
+ animations: ['bounce', 'fadeIn', 'fadeOut', 'slideInUp']
22
+ })
23
+ ]
24
+ };
25
+ ```
26
+
27
+ Apply the base class together with an animation class:
28
+
29
+ ```html
30
+ <div class="keyframe keyframe-fade-in">Content</div>
31
+ ```
32
+
33
+ Animation names are converted to kebab case in generated class names. For example, `fadeInDownBig` becomes `keyframe-fade-in-down-big`.
34
+
35
+ ## Configuration
36
+
37
+ ```js
38
+ require('tailwind-animatecss-kit')({
39
+ prefix: 'motion',
40
+ animations: ['bounce', 'fadeIn'],
41
+ exclude: ['bounce'],
42
+ aliases: {
43
+ enter: 'fadeIn'
44
+ },
45
+ duration: '900ms',
46
+ delay: '0s',
47
+ iterationCount: '1',
48
+ namespaceKeyframes: true,
49
+ respectReducedMotion: true,
50
+ modifiers: true,
51
+ durations: {
52
+ quick: '300ms',
53
+ deliberate: '1400ms'
54
+ },
55
+ delays: {
56
+ short: '150ms',
57
+ long: '800ms'
58
+ },
59
+ repeats: {
60
+ twice: '2',
61
+ forever: 'infinite'
62
+ }
63
+ })
64
+ ```
65
+
66
+ | Option | Default | Purpose |
67
+ | --- | --- | --- |
68
+ | `prefix` | `keyframe` | Prefix for classes and generated keyframes |
69
+ | `animations` | `'*'` | Animate.css names to include; `'*'` includes the full catalog |
70
+ | `exclude` | `[]` | Animation names to omit |
71
+ | `aliases` | `{}` | Public class names mapped to Animate.css animation names |
72
+ | `duration` | `1s` | Default animation duration |
73
+ | `delay` | `0s` | Default animation delay |
74
+ | `iterationCount` | `1` | Default iteration count |
75
+ | `namespaceKeyframes` | `true` | Prefix keyframe identifiers to prevent collisions |
76
+ | `respectReducedMotion` | `true` | Minimize animation when the user requests reduced motion |
77
+ | `modifiers` | `true` | Generate timing, repeat, pause, and run utilities |
78
+ | `durations` | Built-in map | Duration modifier tokens |
79
+ | `delays` | Built-in map | Delay modifier tokens |
80
+ | `repeats` | Built-in map | Iteration modifier tokens |
81
+
82
+ The deprecated `classes` option remains an alias for `animations` during migration.
83
+
84
+ ## Generated utilities
85
+
86
+ With the default prefix, the plugin generates:
87
+
88
+ ```html
89
+ <div class="keyframe keyframe-bounce keyframe-duration-fast keyframe-delay-1s keyframe-repeat-2"></div>
90
+ ```
91
+
92
+ - `keyframe-duration-{token}` sets the duration variable.
93
+ - `keyframe-delay-{token}` sets the delay variable.
94
+ - `keyframe-repeat-{token}` sets the iteration-count variable.
95
+ - `keyframe-paused` and `keyframe-running` control playback.
96
+
97
+ Tailwind variants work normally:
98
+
99
+ ```html
100
+ <div class="keyframe keyframe-fade-in hover:keyframe-paused md:keyframe-duration-slow"></div>
101
+ ```
102
+
103
+ ## Aliases
104
+
105
+ Aliases provide stable semantic names without copying keyframes:
106
+
107
+ ```js
108
+ require('tailwind-animatecss-kit')({
109
+ animations: [],
110
+ aliases: {
111
+ notificationEnter: 'fadeInDown',
112
+ notificationExit: 'fadeOutUp'
113
+ }
114
+ })
115
+ ```
116
+
117
+ ```html
118
+ <aside class="keyframe keyframe-notification-enter"></aside>
119
+ ```
120
+
121
+ ## Catalog
122
+
123
+ The package exposes the animation names found in the installed Animate.css version:
124
+
125
+ ```js
126
+ const keyframes = require('tailwind-animatecss-kit');
127
+ console.log(keyframes.catalog());
128
+ ```
129
+
130
+ ## Migration from 1.x
131
+
132
+ - The default class prefix changed from `animatecss` to `keyframe`.
133
+ - Generated animation class names use kebab case.
134
+ - Keyframe names are namespaced by default.
135
+ - `animations` replaces `classes`; `classes` remains a compatibility alias.
136
+ - `@jasperquinn/postcss-motion-helpers` now processes global timing options and is installed automatically.
137
+ - Reduced-motion handling is enabled by default.
138
+
139
+ ## License
140
+
141
+ MIT
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "tailwind-animatecss-kit",
3
+ "version": "2.0.6",
4
+ "description": "A configurable, namespaced Animate.css integration for Tailwind CSS",
5
+ "main": "plugin.js",
6
+ "exports": "./plugin.js",
7
+ "files": [
8
+ "plugin.js",
9
+ "src",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "homepage": "https://sericpieap.github.io/tailwind-animatecss-kit/",
14
+ "repository": "https://github.com/sericpieap/tailwind-animatecss-kit/",
15
+ "author": "sericpieap",
16
+ "license": "MIT",
17
+ "keywords": [
18
+ "tailwindcss",
19
+ "tailwindcss-plugin",
20
+ "animate.css",
21
+ "animation",
22
+ "keyframes",
23
+ "motion"
24
+ ],
25
+ "scripts": {
26
+ "lint": "eslint . --ext .js",
27
+ "test": "node --test"
28
+ },
29
+ "dependencies": {
30
+ "animate.css": "^4.1.1",
31
+ "postcss": "^8.4.5",
32
+ "@jasperquinn/postcss-motion-helpers": "^1.0.1",
33
+ "postcss-js": "^3.0.3",
34
+ "postcss-selector-parser": "^6.0.8"
35
+ },
36
+ "devDependencies": {
37
+ "eslint": "^8.5.0",
38
+ "eslint-config-airbnb-base": "15.0.0",
39
+ "eslint-plugin-import": "^2.25.2",
40
+ "tailwindcss": "^3.0.7"
41
+ },
42
+ "peerDependencies": {
43
+ "tailwindcss": "^3"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ }
48
+ }
package/plugin.js ADDED
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const tailwindPlugin = require('tailwindcss/plugin');
4
+ const { loadCatalog } = require('./src/catalog');
5
+ const { normalizeOptions } = require('./src/config');
6
+ const { buildStyles } = require('./src/styles');
7
+
8
+ function keyframeTailwindPlugin(options = {}) {
9
+ const config = normalizeOptions(options);
10
+
11
+ return tailwindPlugin(({ addBase, addUtilities }) => {
12
+ const styles = buildStyles(loadCatalog(), config);
13
+ addBase(styles.base);
14
+ addUtilities(styles.utilities);
15
+ });
16
+ }
17
+
18
+ keyframeTailwindPlugin.catalog = () => loadCatalog().names.slice();
19
+ keyframeTailwindPlugin.normalizeOptions = normalizeOptions;
20
+
21
+ module.exports = keyframeTailwindPlugin;
package/src/catalog.js ADDED
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const postcss = require('postcss');
5
+ const selectorParser = require('postcss-selector-parser');
6
+
7
+ let cachedCatalog;
8
+
9
+ function animateCssPath() {
10
+ try {
11
+ return require.resolve('animate.css/animate.css');
12
+ } catch (error) {
13
+ const wrapped = new Error('tailwind-animatecss-kit: unable to resolve animate.css/animate.css');
14
+ wrapped.cause = error;
15
+ throw wrapped;
16
+ }
17
+ }
18
+
19
+ function sourceClasses(selector) {
20
+ const classes = [];
21
+ selectorParser(selectors => selectors.walkClasses(node => classes.push(node.value)))
22
+ .processSync(selector);
23
+ return classes.filter(name => name.startsWith('animate__'));
24
+ }
25
+
26
+ function loadCatalog() {
27
+ if (cachedCatalog) return cachedCatalog;
28
+
29
+ const root = postcss.parse(fs.readFileSync(animateCssPath(), 'utf8'));
30
+ const keyframes = new Map();
31
+ const animations = new Map();
32
+
33
+ root.walkAtRules(/^(?:-webkit-)?keyframes$/, (rule) => {
34
+ if (!keyframes.has(rule.params)) keyframes.set(rule.params, rule.clone());
35
+ });
36
+
37
+ root.walkRules((rule) => {
38
+ const names = sourceClasses(rule.selector)
39
+ .filter(name => name !== 'animate__animated')
40
+ .map(name => name.slice('animate__'.length));
41
+ if (names.length === 0) return;
42
+
43
+ let animationName;
44
+ rule.walkDecls(/^(?:-webkit-)?animation-name$/, declaration => {
45
+ animationName = declaration.value;
46
+ });
47
+ if (!animationName || !keyframes.has(animationName)) return;
48
+
49
+ names.forEach(name => animations.set(name, {
50
+ name,
51
+ keyframeName: animationName,
52
+ declarations: rule.nodes.map(node => node.clone()),
53
+ keyframes: keyframes.get(animationName).clone()
54
+ }));
55
+ });
56
+
57
+ const names = [...animations.keys()].sort();
58
+ cachedCatalog = Object.freeze({ animations, names: Object.freeze(names) });
59
+ return cachedCatalog;
60
+ }
61
+
62
+ module.exports = { loadCatalog, sourceClasses };
package/src/config.js ADDED
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ const DEFAULTS = Object.freeze({
4
+ prefix: 'keyframe',
5
+ animations: '*',
6
+ exclude: [],
7
+ aliases: {},
8
+ duration: '1s',
9
+ delay: '0s',
10
+ iterationCount: '1',
11
+ namespaceKeyframes: true,
12
+ respectReducedMotion: true,
13
+ modifiers: true,
14
+ durations: {
15
+ faster: '500ms',
16
+ fast: '800ms',
17
+ slow: '2s',
18
+ slower: '3s'
19
+ },
20
+ delays: {
21
+ '1s': '1s',
22
+ '2s': '2s',
23
+ '3s': '3s',
24
+ '4s': '4s',
25
+ '5s': '5s'
26
+ },
27
+ repeats: {
28
+ 1: '1',
29
+ 2: '2',
30
+ 3: '3',
31
+ infinite: 'infinite'
32
+ }
33
+ });
34
+
35
+ const isRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value);
36
+
37
+ function assertString(value, path) {
38
+ if (typeof value !== 'string' || value.trim() === '') {
39
+ throw new TypeError(`tailwind-animatecss-kit: ${path} must be a non-empty string`);
40
+ }
41
+ return value.trim();
42
+ }
43
+
44
+ function stringList(value, path, allowWildcard = false) {
45
+ if (allowWildcard && value === '*') return '*';
46
+ if (!Array.isArray(value)) {
47
+ throw new TypeError(`tailwind-animatecss-kit: ${path} must be an array${allowWildcard ? " or '*'" : ''}`);
48
+ }
49
+ return [...new Set(value.map((item, index) => assertString(item, `${path}[${index}]`)))];
50
+ }
51
+
52
+ function stringMap(value, path) {
53
+ if (!isRecord(value)) throw new TypeError(`tailwind-animatecss-kit: ${path} must be an object`);
54
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
55
+ assertString(key, `${path} key`),
56
+ assertString(item, `${path}.${key}`)
57
+ ]));
58
+ }
59
+
60
+ function normalizeOptions(options) {
61
+ if (!isRecord(options)) throw new TypeError('tailwind-animatecss-kit: options must be an object');
62
+
63
+ const config = {
64
+ prefix: assertString(options.prefix ?? DEFAULTS.prefix, 'prefix'),
65
+ animations: stringList(options.animations ?? options.classes ?? DEFAULTS.animations, 'animations', true),
66
+ exclude: stringList(options.exclude ?? DEFAULTS.exclude, 'exclude'),
67
+ aliases: stringMap(options.aliases ?? DEFAULTS.aliases, 'aliases'),
68
+ duration: assertString(options.duration ?? DEFAULTS.duration, 'duration'),
69
+ delay: assertString(options.delay ?? DEFAULTS.delay, 'delay'),
70
+ iterationCount: assertString(options.iterationCount ?? DEFAULTS.iterationCount, 'iterationCount'),
71
+ namespaceKeyframes: options.namespaceKeyframes ?? DEFAULTS.namespaceKeyframes,
72
+ respectReducedMotion: options.respectReducedMotion ?? DEFAULTS.respectReducedMotion,
73
+ modifiers: options.modifiers ?? DEFAULTS.modifiers,
74
+ durations: stringMap(options.durations ?? DEFAULTS.durations, 'durations'),
75
+ delays: stringMap(options.delays ?? DEFAULTS.delays, 'delays'),
76
+ repeats: stringMap(options.repeats ?? DEFAULTS.repeats, 'repeats')
77
+ };
78
+
79
+ ['namespaceKeyframes', 'respectReducedMotion', 'modifiers'].forEach((key) => {
80
+ if (typeof config[key] !== 'boolean') {
81
+ throw new TypeError(`tailwind-animatecss-kit: ${key} must be a boolean`);
82
+ }
83
+ });
84
+
85
+ if (!/^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test(config.prefix)) {
86
+ throw new TypeError('tailwind-animatecss-kit: prefix must be a valid CSS class identifier');
87
+ }
88
+
89
+ return Object.freeze(config);
90
+ }
91
+
92
+ module.exports = { DEFAULTS, normalizeOptions };
package/src/motion.js ADDED
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const postcss = require('postcss');
4
+ const postcssJs = require('postcss-js');
5
+ const postcssAnimateUtils = require('@jasperquinn/postcss-motion-helpers');
6
+
7
+ function toPostcssOptions(config) {
8
+ return {
9
+ variables: {
10
+ enabled: true,
11
+ selector: ':root',
12
+ prefix: 'keyframe',
13
+ preserveExisting: false,
14
+ values: {
15
+ duration: config.duration,
16
+ delay: config.delay,
17
+ iterationCount: config.iterationCount
18
+ }
19
+ },
20
+ rewrite: false,
21
+ namespace: null,
22
+ webkit: false,
23
+ reducedMotion: false
24
+ };
25
+ }
26
+
27
+ function buildMotionBase(config) {
28
+ const processor = postcss([postcssAnimateUtils(toPostcssOptions(config))]);
29
+ const result = processor.process('', { from: undefined }).sync();
30
+ return postcssJs.objectify(result.root);
31
+ }
32
+
33
+ module.exports = { buildMotionBase, toPostcssOptions };
package/src/styles.js ADDED
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const postcss = require('postcss');
4
+ const postcssJs = require('postcss-js');
5
+ const { buildMotionBase } = require('./motion');
6
+
7
+ const kebab = value => value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
8
+
9
+ function selectAnimations(catalog, config) {
10
+ const requested = config.animations === '*' ? catalog.names : config.animations;
11
+ const excluded = new Set(config.exclude);
12
+ const unknown = requested.filter(name => !catalog.animations.has(name));
13
+ const badAliases = Object.entries(config.aliases).filter(([, target]) => !catalog.animations.has(target));
14
+
15
+ if (unknown.length || badAliases.length) {
16
+ const invalid = [...unknown, ...badAliases.map(([alias, target]) => `${alias} -> ${target}`)];
17
+ throw new Error(`tailwind-animatecss-kit: unknown animation(s): ${invalid.join(', ')}`);
18
+ }
19
+
20
+ const selected = requested
21
+ .filter(name => !excluded.has(name))
22
+ .map(name => [name, catalog.animations.get(name)]);
23
+
24
+ Object.entries(config.aliases).forEach(([alias, target]) => {
25
+ if (!excluded.has(alias)) selected.push([alias, catalog.animations.get(target)]);
26
+ });
27
+ return selected;
28
+ }
29
+
30
+ function declarationObject(nodes, keyframeName) {
31
+ return nodes.reduce((result, node) => {
32
+ if (node.type !== 'decl') return result;
33
+ const property = node.prop.replace(/^-(webkit|moz|ms|o)-/, '');
34
+ result[property] = /animation-name$/.test(property) ? keyframeName : node.value;
35
+ return result;
36
+ }, {});
37
+ }
38
+
39
+ function keyframesObject(rule, name) {
40
+ const clone = rule.clone({ name: 'keyframes', params: name });
41
+ const root = postcss.root({ nodes: [clone] });
42
+ return postcssJs.objectify(root);
43
+ }
44
+
45
+ function modifierUtilities(config) {
46
+ if (!config.modifiers) return {};
47
+ const prefix = `.${config.prefix}`;
48
+ const utilities = {
49
+ [`${prefix}-paused`]: { 'animation-play-state': 'paused' },
50
+ [`${prefix}-running`]: { 'animation-play-state': 'running' }
51
+ };
52
+ Object.entries(config.durations).forEach(([token, value]) => {
53
+ utilities[`${prefix}-duration-${token}`] = { '--keyframe-duration': value };
54
+ });
55
+ Object.entries(config.delays).forEach(([token, value]) => {
56
+ utilities[`${prefix}-delay-${token}`] = { '--keyframe-delay': value };
57
+ });
58
+ Object.entries(config.repeats).forEach(([token, value]) => {
59
+ utilities[`${prefix}-repeat-${token}`] = { '--keyframe-iteration-count': value };
60
+ });
61
+ return utilities;
62
+ }
63
+
64
+ function buildStyles(catalog, config) {
65
+ const baseSelector = `.${config.prefix}`;
66
+ const base = buildMotionBase(config);
67
+ const utilities = {
68
+ [baseSelector]: {
69
+ 'animation-duration': 'var(--keyframe-duration)',
70
+ 'animation-delay': 'var(--keyframe-delay)',
71
+ 'animation-iteration-count': 'var(--keyframe-iteration-count)',
72
+ 'animation-fill-mode': 'both'
73
+ },
74
+ ...modifierUtilities(config)
75
+ };
76
+
77
+ selectAnimations(catalog, config).forEach(([publicName, animation]) => {
78
+ const keyframeName = config.namespaceKeyframes
79
+ ? `${config.prefix}-${kebab(publicName)}`
80
+ : animation.keyframeName;
81
+ Object.assign(base, keyframesObject(animation.keyframes, keyframeName));
82
+ utilities[`${baseSelector}.${config.prefix}-${kebab(publicName)}`] = declarationObject(
83
+ animation.declarations,
84
+ keyframeName
85
+ );
86
+ });
87
+
88
+ if (config.respectReducedMotion) {
89
+ base['@media (prefers-reduced-motion: reduce)'] = {
90
+ [baseSelector]: {
91
+ 'animation-duration': '1ms !important',
92
+ 'animation-iteration-count': '1 !important',
93
+ 'transition-duration': '1ms !important'
94
+ }
95
+ };
96
+ }
97
+
98
+ return { base, utilities };
99
+ }
100
+
101
+ module.exports = { buildStyles, kebab, selectAnimations };