├── .gitignore ├── .release-it.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "tagName": "v${version}", 4 | "requireCleanWorkingDir": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [5.0.0] - 2020-08-04 9 | 10 | ### Added 11 | - Added a `group-focus-visible` variant 12 | 13 | ### Removed 14 | - Removed the `checked` variant since it is built into Tailwind as of v1.5 15 | 16 | ## [4.0.0] - 2020-05-09 17 | 18 | ### Removed 19 | - Removed the `group-focus` variant since it is now built into Tailwind 20 | 21 | ## [3.1.1] - 2020-03-17 22 | 23 | ### Fixed 24 | - Fixed the `can-hover` and `no-hover` variants with Tailwind’s `important` option set to a selector (e.g. `#app`) 25 | 26 | ## [3.1.0] - 2020-02-13 27 | 28 | ### Added 29 | - Added `can-hover` and `no-hover` variants 30 | 31 | ## [3.0.0] - 2020-02-05 32 | 33 | ### Added 34 | - Added a `checked` variant 35 | 36 | ### Changed 37 | - Changed to use Tailwind 1.2’s new plugin definition syntax 38 | 39 | ## [2.4.0] - 2019-12-20 40 | 41 | ### Added 42 | - Added `group-visited` and `group-disabled` variants 43 | 44 | ### Fixed 45 | - Tailwind’s `prefix` option is now properly applied to the `.group` part of the selector for group variants 46 | 47 | ## [2.3.0] - 2019-11-29 48 | 49 | ### Added 50 | - Added a `group-focus-within` variant 51 | 52 | ## [2.2.0] - 2019-09-02 53 | 54 | ### Removed 55 | - Removed the `visited` variant since it is now built into Tailwind 56 | 57 | ## [2.1.0] - 2019-07-11 58 | 59 | ### Added 60 | - Added support for utilities that include pseudo-elements (based on Tailwind’s method introduced in v1.0.5) 61 | 62 | ## [2.0.0] - 2019-05-13 63 | 64 | No change since 2.0.0-beta.1 65 | 66 | ## [2.0.0-beta.1] - 2019-04-07 67 | 68 | ### Added 69 | - Tailwind 1.0.0 compatibility 70 | 71 | ## [1.0.0] - 2019-02-14 72 | 73 | Initial release 74 | 75 | [Unreleased]: https://github.com/benface/tailwindcss-interaction-variants/compare/v5.0.0...HEAD 76 | [5.0.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v4.0.0...v5.0.0 77 | [4.0.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v3.1.1...v4.0.0 78 | [3.1.1]: https://github.com/benface/tailwindcss-interaction-variants/compare/v3.1.0...v3.1.1 79 | [3.1.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v3.0.0...v3.1.0 80 | [3.0.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.4.0...v3.0.0 81 | [2.4.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.3.0...v2.4.0 82 | [2.3.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.2.0...v2.3.0 83 | [2.2.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.1.0...v2.2.0 84 | [2.1.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.0.0...v2.1.0 85 | [2.0.0]: https://github.com/benface/tailwindcss-interaction-variants/compare/v2.0.0-beta.1...v2.0.0 86 | [2.0.0-beta.1]: https://github.com/benface/tailwindcss-interaction-variants/compare/v1.0.0...v2.0.0-beta.1 87 | [1.0.0]: https://github.com/benface/tailwindcss-interaction-variants/releases/tag/v1.0.0 88 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # ISC License 2 | 3 | Copyright (c) Benoît Rouleau 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⛔️ DEPRECATED 2 | 3 | Tailwind CSS’s [JIT engine](https://tailwindcss.com/docs/upgrade-guide#migrating-to-the-jit-engine), which completely replaced the classic engine in Tailwind 3.0, supports _all_ possible variants, current and future, rendering this plugin useless. So please use it instead. Thank you! 4 | 5 | # Interaction Variants Plugin for Tailwind CSS 6 | 7 | ## Requirements 8 | 9 | This plugin requires Tailwind CSS 1.2 or later. If your project uses an older version of Tailwind, you should install the latest 2.x version of this plugin (`npm install tailwindcss-interaction-variants@2.x`). 10 | 11 | ## Installation 12 | 13 | ```bash 14 | npm install tailwindcss-interaction-variants 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | // tailwind.config.js 21 | module.exports = { 22 | theme: { 23 | backgroundColor: { 24 | 'black': 'black', 25 | }, 26 | }, 27 | variants: { 28 | backgroundColor: ['group-focus-within', 'group-focus-visible', 'group-active', 'group-visited', 'group-disabled', 'hocus', 'group-hocus', 'can-hover', 'no-hover'], 29 | }, 30 | plugins: [ 31 | require('tailwindcss-interaction-variants'), 32 | ], 33 | }; 34 | ``` 35 | 36 | The above configuration would generate the following CSS: 37 | 38 | ```css 39 | .bg-black { 40 | background-color: black; 41 | } 42 | 43 | .group:focus-within .group-focus-within\:bg-black { 44 | background-color: black; 45 | } 46 | 47 | .group:focus-visible .group-focus-visible\:bg-black { 48 | background-color: black; 49 | } 50 | 51 | .group:active .group-active\:bg-black { 52 | background-color: black; 53 | } 54 | 55 | .group:visited .group-visited\:bg-black { 56 | background-color: black; 57 | } 58 | 59 | .group:disabled .group-disabled\:bg-black { 60 | background-color: black; 61 | } 62 | 63 | .hocus\:bg-black:hover, .hocus\:bg-black:focus { 64 | background-color: black; 65 | } 66 | 67 | .group:hover .group-hocus\:bg-black, .group:focus .group-hocus\:bg-black { 68 | background-color: black; 69 | } 70 | 71 | @media (hover: hover) { 72 | .can-hover\:bg-black { 73 | background-color: black; 74 | } 75 | } 76 | 77 | @media (hover: none) { 78 | .no-hover\:bg-black { 79 | background-color: black; 80 | } 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin'); 2 | const _ = require('lodash'); 3 | const selectorParser = require('postcss-selector-parser'); 4 | 5 | module.exports = plugin(function({ addVariant, config, e, postcss }) { 6 | const importantSelector = typeof config('important') === 'string' ? config('important') : null; 7 | 8 | const prefixClass = function(className) { 9 | const prefix = config('prefix'); 10 | const getPrefix = typeof prefix === 'function' ? prefix : () => prefix; 11 | return `${getPrefix(`.${className}`)}${className}`; 12 | }; 13 | 14 | const pseudoClassVariant = function(pseudoClass) { 15 | return ({ modifySelectors, separator }) => { 16 | return modifySelectors(({ selector }) => { 17 | return selectorParser(selectors => { 18 | selectors.walkClasses(classNode => { 19 | classNode.value = `${pseudoClass}${separator}${classNode.value}`; 20 | classNode.parent.insertAfter(classNode, selectorParser.pseudo({ value: `:${pseudoClass}` })); 21 | }); 22 | }).processSync(selector); 23 | }); 24 | }; 25 | }; 26 | 27 | const groupPseudoClassVariant = function(pseudoClass) { 28 | return ({ modifySelectors, separator }) => { 29 | return modifySelectors(({ selector }) => { 30 | return selectorParser(selectors => { 31 | selectors.walkClasses(classNode => { 32 | classNode.value = `group-${pseudoClass}${separator}${classNode.value}`; 33 | classNode.parent.insertBefore(classNode, selectorParser().astSync(`.${prefixClass('group')}:${pseudoClass} `)); 34 | }); 35 | }).processSync(selector); 36 | }); 37 | }; 38 | }; 39 | 40 | addVariant('group-focus-within', groupPseudoClassVariant('focus-within')); 41 | addVariant('group-focus-visible', groupPseudoClassVariant('focus-visible')); 42 | addVariant('group-active', groupPseudoClassVariant('active')); 43 | addVariant('group-visited', groupPseudoClassVariant('visited')); 44 | addVariant('group-disabled', groupPseudoClassVariant('disabled')); 45 | 46 | addVariant('hocus', ({ modifySelectors, separator }) => { 47 | modifySelectors(({ selector }) => { 48 | return selectorParser(selectors => { 49 | const clonedSelectors = selectors.clone(); 50 | [selectors, clonedSelectors].forEach((sel, i) => { 51 | sel.walkClasses(classNode => { 52 | classNode.value = `hocus${separator}${classNode.value}`; 53 | classNode.parent.insertAfter(classNode, selectorParser.pseudo({ value: `:${i === 0 ? 'hover' : 'focus'}` })); 54 | }); 55 | }); 56 | selectors.append(clonedSelectors); 57 | }).processSync(selector); 58 | }); 59 | }); 60 | 61 | addVariant('group-hocus', ({ modifySelectors, separator }) => { 62 | modifySelectors(({ selector }) => { 63 | return selectorParser(selectors => { 64 | const clonedSelectors = selectors.clone(); 65 | [selectors, clonedSelectors].forEach((sel, i) => { 66 | sel.walkClasses(classNode => { 67 | classNode.value = `group-hocus${separator}${classNode.value}`; 68 | classNode.parent.insertBefore(classNode, selectorParser().astSync(`.${prefixClass('group')}:${i === 0 ? 'hover' : 'focus'} `)); 69 | }); 70 | }); 71 | selectors.append(clonedSelectors); 72 | }).processSync(selector) 73 | }); 74 | }); 75 | 76 | addVariant('can-hover', ({ container, separator }) => { 77 | const atRule = postcss.atRule({ name: 'media', params: '(hover: hover)' }); 78 | atRule.append(container.nodes); 79 | container.append(atRule); 80 | atRule.walkRules(rule => { 81 | rule.selector = `${importantSelector ? importantSelector + ' ' : ''}.${e(`can-hover${separator}`)}${rule.selector.slice(1 + (importantSelector ? importantSelector.length + 1 : 0))}`; 82 | }); 83 | }); 84 | 85 | addVariant('no-hover', ({ container, separator }) => { 86 | const atRule = postcss.atRule({ name: 'media', params: '(hover: none)' }); 87 | atRule.append(container.nodes); 88 | container.append(atRule); 89 | atRule.walkRules(rule => { 90 | rule.selector = `${importantSelector ? importantSelector + ' ' : ''}.${e(`no-hover${separator}`)}${rule.selector.slice(1 + (importantSelector ? importantSelector.length + 1 : 0))}`; 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-interaction-variants", 3 | "version": "5.0.0", 4 | "description": "Tailwind CSS plugin to add some missing interaction state variants", 5 | "author": "Benoît Rouleau ", 6 | "license": "ISC", 7 | "repository": "https://github.com/benface/tailwindcss-interaction-variants.git", 8 | "bugs": "https://github.com/benface/tailwindcss-interaction-variants/issues", 9 | "homepage": "https://github.com/benface/tailwindcss-interaction-variants", 10 | "scripts": { 11 | "test": "jest", 12 | "release": "f(){ release-it $1 && github-release-from-changelog ;};f" 13 | }, 14 | "dependencies": { 15 | "lodash": "^4.17.19", 16 | "postcss-selector-parser": "^6.0.2" 17 | }, 18 | "devDependencies": { 19 | "github-release-from-changelog": "^2.1.1", 20 | "jest": "^26.2.2", 21 | "jest-matcher-css": "^1.1.0", 22 | "postcss": "^7.0.32", 23 | "release-it": "^13.6.6", 24 | "tailwindcss": "^1.6.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const cssMatcher = require('jest-matcher-css'); 3 | const postcss = require('postcss'); 4 | const tailwindcss = require('tailwindcss'); 5 | const interactionVariantsPlugin = require('./index.js'); 6 | 7 | const generatePluginCss = (variants = [], tailwindOptions = {}, css = null) => { 8 | return postcss( 9 | tailwindcss({ 10 | theme: { 11 | screens: { 12 | 'sm': '640px', 13 | }, 14 | }, 15 | corePlugins: false, 16 | plugins: [ 17 | interactionVariantsPlugin, 18 | ({ addUtilities }) => { 19 | addUtilities(css ? css : { 20 | '.w-1\\/2': { 21 | 'width': '50%', 22 | }, 23 | }, variants); 24 | }, 25 | ], 26 | ...tailwindOptions, 27 | }) 28 | ) 29 | .process('@tailwind utilities', { 30 | from: undefined, 31 | }) 32 | .then(result => { 33 | return result.css; 34 | }); 35 | }; 36 | 37 | expect.extend({ 38 | toMatchCss: cssMatcher, 39 | }); 40 | 41 | test('the plugin doesn’t do anything if the variants aren’t used', () => { 42 | return generatePluginCss().then(css => { 43 | expect(css).toMatchCss(` 44 | .w-1\\/2 { 45 | width: 50%; 46 | } 47 | `); 48 | }); 49 | }); 50 | 51 | test('the hocus variant is working', () => { 52 | return generatePluginCss(['hocus']).then(css => { 53 | expect(css).toMatchCss(` 54 | .w-1\\/2 { 55 | width: 50%; 56 | } 57 | .hocus\\:w-1\\/2:hover, .hocus\\:w-1\\/2:focus { 58 | width: 50%; 59 | } 60 | `); 61 | }); 62 | }); 63 | 64 | test('the group-hocus variant is working', () => { 65 | return generatePluginCss(['group-hocus']).then(css => { 66 | expect(css).toMatchCss(` 67 | .w-1\\/2 { 68 | width: 50%; 69 | } 70 | .group:hover .group-hocus\\:w-1\\/2, .group:focus .group-hocus\\:w-1\\/2 { 71 | width: 50%; 72 | } 73 | `); 74 | }); 75 | }); 76 | 77 | test('the can-hover variant is working', () => { 78 | return generatePluginCss(['can-hover']).then(css => { 79 | expect(css).toMatchCss(` 80 | .w-1\\/2 { 81 | width: 50%; 82 | } 83 | @media (hover: hover) { 84 | .can-hover\\:w-1\\/2 { 85 | width: 50%; 86 | } 87 | } 88 | `); 89 | }); 90 | }); 91 | 92 | test('the no-hover variant is working', () => { 93 | return generatePluginCss(['no-hover']).then(css => { 94 | expect(css).toMatchCss(` 95 | .w-1\\/2 { 96 | width: 50%; 97 | } 98 | @media (hover: none) { 99 | .no-hover\\:w-1\\/2 { 100 | width: 50%; 101 | } 102 | } 103 | `); 104 | }); 105 | }); 106 | 107 | test('all the variants are working', () => { 108 | return generatePluginCss(['group-focus-within', 'group-focus-visible', 'group-active', 'group-visited', 'group-disabled', 'hocus', 'group-hocus', 'can-hover', 'no-hover']).then(css => { 109 | expect(css).toMatchCss(` 110 | .w-1\\/2 { 111 | width: 50%; 112 | } 113 | .group:focus-within .group-focus-within\\:w-1\\/2 { 114 | width: 50%; 115 | } 116 | .group:focus-visible .group-focus-visible\\:w-1\\/2 { 117 | width: 50%; 118 | } 119 | .group:active .group-active\\:w-1\\/2 { 120 | width: 50%; 121 | } 122 | .group:visited .group-visited\\:w-1\\/2 { 123 | width: 50%; 124 | } 125 | .group:disabled .group-disabled\\:w-1\\/2 { 126 | width: 50%; 127 | } 128 | .hocus\\:w-1\\/2:hover, .hocus\\:w-1\\/2:focus { 129 | width: 50%; 130 | } 131 | .group:hover .group-hocus\\:w-1\\/2, .group:focus .group-hocus\\:w-1\\/2 { 132 | width: 50%; 133 | } 134 | @media (hover: hover) { 135 | .can-hover\\:w-1\\/2 { 136 | width: 50%; 137 | } 138 | } 139 | @media (hover: none) { 140 | .no-hover\\:w-1\\/2 { 141 | width: 50%; 142 | } 143 | } 144 | `); 145 | }); 146 | }); 147 | 148 | test('all variants can be chained with the responsive variant', () => { 149 | return generatePluginCss(['group-focus-within', 'group-focus-visible', 'group-active', 'group-visited', 'group-disabled', 'hocus', 'group-hocus', 'can-hover', 'no-hover', 'responsive']).then(css => { 150 | expect(css).toMatchCss(` 151 | .w-1\\/2 { 152 | width: 50%; 153 | } 154 | .group:focus-within .group-focus-within\\:w-1\\/2 { 155 | width: 50%; 156 | } 157 | .group:focus-visible .group-focus-visible\\:w-1\\/2 { 158 | width: 50%; 159 | } 160 | .group:active .group-active\\:w-1\\/2 { 161 | width: 50%; 162 | } 163 | .group:visited .group-visited\\:w-1\\/2 { 164 | width: 50%; 165 | } 166 | .group:disabled .group-disabled\\:w-1\\/2 { 167 | width: 50%; 168 | } 169 | .hocus\\:w-1\\/2:hover, .hocus\\:w-1\\/2:focus { 170 | width: 50%; 171 | } 172 | .group:hover .group-hocus\\:w-1\\/2, .group:focus .group-hocus\\:w-1\\/2 { 173 | width: 50%; 174 | } 175 | @media (hover: hover) { 176 | .can-hover\\:w-1\\/2 { 177 | width: 50%; 178 | } 179 | } 180 | @media (hover: none) { 181 | .no-hover\\:w-1\\/2 { 182 | width: 50%; 183 | } 184 | } 185 | @media (min-width: 640px) { 186 | .sm\\:w-1\\/2 { 187 | width: 50%; 188 | } 189 | .group:focus-within .sm\\:group-focus-within\\:w-1\\/2 { 190 | width: 50%; 191 | } 192 | .group:focus-visible .sm\\:group-focus-visible\\:w-1\\/2 { 193 | width: 50%; 194 | } 195 | .group:active .sm\\:group-active\\:w-1\\/2 { 196 | width: 50%; 197 | } 198 | .group:visited .sm\\:group-visited\\:w-1\\/2 { 199 | width: 50%; 200 | } 201 | .group:disabled .sm\\:group-disabled\\:w-1\\/2 { 202 | width: 50%; 203 | } 204 | .sm\\:hocus\\:w-1\\/2:hover, .sm\\:hocus\\:w-1\\/2:focus { 205 | width: 50%; 206 | } 207 | .group:hover .sm\\:group-hocus\\:w-1\\/2, .group:focus .sm\\:group-hocus\\:w-1\\/2 { 208 | width: 50%; 209 | } 210 | @media (hover: hover) { 211 | .sm\\:can-hover\\:w-1\\/2 { 212 | width: 50%; 213 | } 214 | } 215 | @media (hover: none) { 216 | .sm\\:no-hover\\:w-1\\/2 { 217 | width: 50%; 218 | } 219 | } 220 | } 221 | `); 222 | }); 223 | }); 224 | 225 | test('the variants work with Tailwind’s prefix option', () => { 226 | return generatePluginCss(['hover', 'hocus', 'group-hocus', 'can-hover', 'no-hover'], { 227 | prefix: 'tw-', 228 | }).then(css => { 229 | expect(css).toMatchCss(` 230 | .tw-w-1\\/2 { 231 | width: 50%; 232 | } 233 | .hover\\:tw-w-1\\/2:hover { 234 | width: 50%; 235 | } 236 | .hocus\\:tw-w-1\\/2:hover, .hocus\\:tw-w-1\\/2:focus { 237 | width: 50%; 238 | } 239 | .tw-group:hover .group-hocus\\:tw-w-1\\/2, .tw-group:focus .group-hocus\\:tw-w-1\\/2 { 240 | width: 50%; 241 | } 242 | @media (hover: hover) { 243 | .can-hover\\:tw-w-1\\/2 { 244 | width: 50%; 245 | } 246 | } 247 | @media (hover: none) { 248 | .no-hover\\:tw-w-1\\/2 { 249 | width: 50%; 250 | } 251 | } 252 | `); 253 | }); 254 | }); 255 | 256 | test('the variants work with Tailwind’s important option', () => { 257 | return generatePluginCss(['hover', 'hocus', 'group-hocus', 'can-hover', 'no-hover'], { 258 | important: true, 259 | }).then(css => { 260 | expect(css).toMatchCss(` 261 | .w-1\\/2 { 262 | width: 50% !important; 263 | } 264 | .hover\\:w-1\\/2:hover { 265 | width: 50% !important; 266 | } 267 | .hocus\\:w-1\\/2:hover, .hocus\\:w-1\\/2:focus { 268 | width: 50% !important; 269 | } 270 | .group:hover .group-hocus\\:w-1\\/2, .group:focus .group-hocus\\:w-1\\/2 { 271 | width: 50% !important; 272 | } 273 | @media (hover: hover) { 274 | .can-hover\\:w-1\\/2 { 275 | width: 50% !important; 276 | } 277 | } 278 | @media (hover: none) { 279 | .no-hover\\:w-1\\/2 { 280 | width: 50% !important; 281 | } 282 | } 283 | `); 284 | }); 285 | }); 286 | 287 | test('the variants work with Tailwind’s important option set to a selector', () => { 288 | return generatePluginCss(['hover', 'hocus', 'group-hocus', 'can-hover', 'no-hover'], { 289 | important: '#app', 290 | }).then(css => { 291 | expect(css).toMatchCss(` 292 | #app .w-1\\/2 { 293 | width: 50%; 294 | } 295 | #app .hover\\:w-1\\/2:hover { 296 | width: 50%; 297 | } 298 | #app .hocus\\:w-1\\/2:hover, #app .hocus\\:w-1\\/2:focus { 299 | width: 50%; 300 | } 301 | #app .group:hover .group-hocus\\:w-1\\/2, #app .group:focus .group-hocus\\:w-1\\/2 { 302 | width: 50%; 303 | } 304 | @media (hover: hover) { 305 | #app .can-hover\\:w-1\\/2 { 306 | width: 50%; 307 | } 308 | } 309 | @media (hover: none) { 310 | #app .no-hover\\:w-1\\/2 { 311 | width: 50%; 312 | } 313 | } 314 | `); 315 | }); 316 | }); 317 | 318 | test('the variants work on utilities that include pseudo-elements', () => { 319 | return generatePluginCss(['hover', 'hocus', 'group-hocus', 'can-hover', 'no-hover'], {}, { 320 | '.placeholder-gray-400::placeholder': { 321 | 'color': '#cbd5e0', 322 | }, 323 | }).then(css => { 324 | expect(css).toMatchCss(` 325 | .placeholder-gray-400::placeholder { 326 | color: #cbd5e0; 327 | } 328 | .hover\\:placeholder-gray-400:hover::placeholder { 329 | color: #cbd5e0; 330 | } 331 | .hocus\\:placeholder-gray-400:hover::placeholder, .hocus\\:placeholder-gray-400:focus::placeholder { 332 | color: #cbd5e0; 333 | } 334 | .group:hover .group-hocus\\:placeholder-gray-400::placeholder, .group:focus .group-hocus\\:placeholder-gray-400::placeholder { 335 | color: #cbd5e0; 336 | } 337 | @media (hover: hover) { 338 | .can-hover\\:placeholder-gray-400::placeholder { 339 | color: #cbd5e0; 340 | } 341 | } 342 | @media (hover: none) { 343 | .no-hover\\:placeholder-gray-400::placeholder { 344 | color: #cbd5e0; 345 | } 346 | } 347 | `); 348 | }); 349 | }); 350 | --------------------------------------------------------------------------------