├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .rollup.js ├── .tape.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE.md ├── README.md ├── package.json ├── src ├── index.d.ts └── index.js └── test ├── basic.css ├── basic.expect.css ├── basic.preserve.expect.css └── basic.replacewith.expect.css /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{json,md,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node: [12, 16] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: ${{ matrix.node }} 16 | 17 | - run: yarn install --ignore-scripts 18 | - run: yarn run test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | *.log* 6 | *.result.css 7 | .* 8 | !.editorconfig 9 | !.gitattributes 10 | !.gitignore 11 | !.rollup.js 12 | !.tape.js 13 | !.github 14 | -------------------------------------------------------------------------------- /.rollup.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | 3 | export default { 4 | ...pkg.rollup, 5 | plugins: pkg.rollup.plugins.map(plugin => ((plugin = [].concat(plugin)), require(plugin[0])(...plugin.slice(1)))), 6 | onwarn(warning, warn) { 7 | if (warning.code !== 'UNRESOLVED_IMPORT') warn(warning) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.tape.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'basic': { 3 | message: 'supports basic usage' 4 | }, 5 | 'basic:replacewith': { 6 | message: 'supports { replaceWith: ".focus-within" } usage', 7 | options: { 8 | replaceWith: '.focus-within' 9 | } 10 | }, 11 | 'basic:preserve': { 12 | message: 'supports { preserve: false } usage', 13 | options: { 14 | preserve: false 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes to PostCSS Focus Within 2 | 3 | ### 5.0.1 (September 22, 2021) 4 | 5 | - Added missing `dist` to bundle. 6 | - Added missing `exports` to `package.json` 7 | - Added missing `types` to `package.json` 8 | - Added bundling & testing as prepublish step. 9 | 10 | ### 5.0.0 (September 17, 2021) 11 | 12 | - Updated: Support for PostCS 8+ (major). 13 | - Updated: Support for Node 12+ (major). 14 | 15 | ### 4.0.0 (April 20, 2020) 16 | 17 | - Fixed: Allow `:focus-within` to appear escaped in a selector 18 | - Updated: Support for Node 10+ 19 | - Updated: Ownership moved to CSS Tools 20 | 21 | ### 3.0.0 (September 17, 2018) 22 | 23 | - Updated: Support for PostCSS v7+ 24 | - Updated: Support for Node v6+ 25 | 26 | ### 2.0.0 (April 7, 2018) 27 | 28 | - Changed: default functionality to preserve the original rule 29 | - Added: `preserve` option to preserve the original rule using `:focus-within` 30 | 31 | ### 1.0.0 (February 17, 2018) 32 | 33 | - Initial version 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PostCSS Focus Within 2 | 3 | You want to help? You rock! Now, take a moment to be sure your contributions 4 | make sense to everyone else. 5 | 6 | ## Reporting Issues 7 | 8 | Found a problem? Want a new feature? 9 | 10 | - See if your issue or idea has [already been reported]. 11 | - Provide a [reduced test case] or a [live example]. 12 | 13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code. 14 | 15 | ## Submitting Pull Requests 16 | 17 | Pull requests are the greatest contributions, so be sure they are focused in 18 | scope and avoid unrelated commits. 19 | 20 | 1. To begin; [fork this project], clone your fork, and add our upstream. 21 | ```bash 22 | # Clone your fork of the repo into the current directory 23 | git clone git@github.com:YOUR_USER/postcss-focus-within.git 24 | 25 | # Navigate to the newly cloned directory 26 | cd postcss-focus-within 27 | 28 | # Assign the original repo to a remote called "upstream" 29 | git remote add upstream git@github.com:csstools/postcss-focus-within.git 30 | 31 | # Install the tools necessary for testing 32 | npm install 33 | ``` 34 | 35 | 2. Create a branch for your feature or fix: 36 | ```bash 37 | # Move into a new branch for your feature 38 | git checkout -b feature/thing 39 | ``` 40 | ```bash 41 | # Move into a new branch for your fix 42 | git checkout -b fix/something 43 | ``` 44 | 45 | 3. If your code follows our practices, then push your feature branch: 46 | ```bash 47 | # Test current code 48 | npm test 49 | ``` 50 | ```bash 51 | # Push the branch for your new feature 52 | git push origin feature/thing 53 | ``` 54 | ```bash 55 | # Or, push the branch for your update 56 | git push origin update/something 57 | ``` 58 | 59 | That’s it! Now [open a pull request] with a clear title and description. 60 | 61 | [already been reported]: issues 62 | [fork this project]: fork 63 | [live example]: https://codepen.io/pen 64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/ 66 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing PostCSS 2 | 3 | [PostCSS] runs in all Node environments, with special instructions for: 4 | 5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | 6 | | --- | --- | --- | --- | --- | --- | 7 | 8 | ## Node 9 | 10 | Add [PostCSS] to your project: 11 | 12 | ```bash 13 | npm install postcss-focus-within --save-dev 14 | ``` 15 | 16 | Use [PostCSS] to process your CSS: 17 | 18 | ```js 19 | const postcssFocusWithin = require('postcss-focus-within'); 20 | 21 | postcssFocusWithin.process(YOUR_CSS /*, processOptions, pluginOptions */); 22 | ``` 23 | 24 | Or use it as a [PostCSS] plugin: 25 | 26 | ```js 27 | const postcss = require('postcss'); 28 | const postcssFocusWithin = require('postcss-focus-within'); 29 | 30 | postcss([ 31 | postcssFocusWithin(/* pluginOptions */) 32 | ]).process(YOUR_CSS /*, processOptions */); 33 | ``` 34 | 35 | ## PostCSS CLI 36 | 37 | Add [PostCSS CLI] to your project: 38 | 39 | ```bash 40 | npm install postcss-cli --save-dev 41 | ``` 42 | 43 | Use [PostCSS] in your `postcss.config.js` configuration file: 44 | 45 | ```js 46 | const postcssFocusWithin = require('postcss-focus-within'); 47 | 48 | module.exports = { 49 | plugins: [ 50 | postcssFocusWithin(/* pluginOptions */) 51 | ] 52 | } 53 | ``` 54 | 55 | ## Webpack 56 | 57 | Add [PostCSS Loader] to your project: 58 | 59 | ```bash 60 | npm install postcss-loader --save-dev 61 | ``` 62 | 63 | Use [PostCSS] in your Webpack configuration: 64 | 65 | ```js 66 | const postcssFocusWithin = require('postcss-focus-within'); 67 | 68 | module.exports = { 69 | module: { 70 | rules: [ 71 | { 72 | test: /\.css$/, 73 | use: [ 74 | 'style-loader', 75 | { loader: 'css-loader', options: { importLoaders: 1 } }, 76 | { loader: 'postcss-loader', options: { 77 | ident: 'postcss', 78 | plugins: () => [ 79 | postcssFocusWithin(/* pluginOptions */) 80 | ] 81 | } } 82 | ] 83 | } 84 | ] 85 | } 86 | } 87 | ``` 88 | 89 | ## Create React App 90 | 91 | Add [React App Rewired] and [React App Rewire PostCSS] to your project: 92 | 93 | ```bash 94 | npm install react-app-rewired react-app-rewire-postcss --save-dev 95 | ``` 96 | 97 | Use [React App Rewire PostCSS] and [PostCSS] in your 98 | `config-overrides.js` file: 99 | 100 | ```js 101 | const reactAppRewirePostcss = require('react-app-rewire-postcss'); 102 | const postcssFocusWithin = require('postcss-focus-within'); 103 | 104 | module.exports = config => reactAppRewirePostcss(config, { 105 | plugins: () => [ 106 | postcssFocusWithin(/* pluginOptions */) 107 | ] 108 | }); 109 | ``` 110 | 111 | ## Gulp 112 | 113 | Add [Gulp PostCSS] to your project: 114 | 115 | ```bash 116 | npm install gulp-postcss --save-dev 117 | ``` 118 | 119 | Use [PostCSS] in your Gulpfile: 120 | 121 | ```js 122 | const postcss = require('gulp-postcss'); 123 | const postcssFocusWithin = require('postcss-focus-within'); 124 | 125 | gulp.task('css', () => gulp.src('./src/*.css').pipe( 126 | postcss([ 127 | postcssFocusWithin(/* pluginOptions */) 128 | ]) 129 | ).pipe( 130 | gulp.dest('.') 131 | )); 132 | ``` 133 | 134 | ## Grunt 135 | 136 | Add [Grunt PostCSS] to your project: 137 | 138 | ```bash 139 | npm install grunt-postcss --save-dev 140 | ``` 141 | 142 | Use [PostCSS] in your Gruntfile: 143 | 144 | ```js 145 | const postcssFocusWithin = require('postcss-focus-within'); 146 | 147 | grunt.loadNpmTasks('grunt-postcss'); 148 | 149 | grunt.initConfig({ 150 | postcss: { 151 | options: { 152 | use: [ 153 | postcssFocusWithin(/* pluginOptions */) 154 | ] 155 | }, 156 | dist: { 157 | src: '*.css' 158 | } 159 | } 160 | }); 161 | ``` 162 | 163 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss 164 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss 165 | [PostCSS]: https://github.com/postcss/postcss 166 | [PostCSS CLI]: https://github.com/postcss/postcss-cli 167 | [PostCSS Loader]: https://github.com/postcss/postcss-loader 168 | [PostCSS]: https://github.com/csstools/postcss-focus-within 169 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss 170 | [React App Rewired]: https://github.com/timarney/react-app-rewired 171 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # CC0 1.0 Universal 2 | 3 | ## Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an “owner”) of an original work of 8 | authorship and/or a database (each, a “Work”). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific works 12 | (“Commons”) that the public can reliably and without fear of later claims of 13 | infringement build upon, modify, incorporate in other works, reuse and 14 | redistribute as freely as possible in any form whatsoever and for any purposes, 15 | including without limitation commercial purposes. These owners may contribute 16 | to the Commons to promote the ideal of a free culture and the further 17 | production of creative, cultural and scientific works, or to gain reputation or 18 | greater distribution for their Work in part through the use and efforts of 19 | others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation of 22 | additional consideration or compensation, the person associating CC0 with a 23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and 25 | publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights (“Copyright and 31 | Related Rights”). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate, 34 | and translate a Work; 35 | 2. moral rights retained by the original author(s) and/or performer(s); 36 | 3. publicity and privacy rights pertaining to a person’s image or likeness 37 | depicted in a Work; 38 | 4. rights protecting against unfair competition in regards to a Work, 39 | subject to the limitations in paragraph 4(i), below; 40 | 5. rights protecting the extraction, dissemination, use and reuse of data in 41 | a Work; 42 | 6. database rights (such as those arising under Directive 96/9/EC of the 43 | European Parliament and of the Council of 11 March 1996 on the legal 44 | protection of databases, and under any national implementation thereof, 45 | including any amended or successor version of such directive); and 46 | 7. other similar, equivalent or corresponding rights throughout the world 47 | based on applicable law or treaty, and any national implementations 48 | thereof. 49 | 50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright 53 | and Related Rights and associated claims and causes of action, whether now 54 | known or unknown (including existing as well as future claims and causes of 55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 56 | duration provided by applicable law or treaty (including future time 57 | extensions), (iii) in any current or future medium and for any number of 58 | copies, and (iv) for any purpose whatsoever, including without limitation 59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer 60 | makes the Waiver for the benefit of each member of the public at large and 61 | to the detriment of Affirmer’s heirs and successors, fully intending that 62 | such Waiver shall not be subject to revocation, rescission, cancellation, 63 | termination, or any other legal or equitable action to disrupt the quiet 64 | enjoyment of the Work by the public as contemplated by Affirmer’s express 65 | Statement of Purpose. 66 | 67 | 3. Public License Fallback. Should any part of the Waiver for any reason be 68 | judged legally invalid or ineffective under applicable law, then the Waiver 69 | shall be preserved to the maximum extent permitted taking into account 70 | Affirmer’s express Statement of Purpose. In addition, to the extent the 71 | Waiver is so judged Affirmer hereby grants to each affected person a 72 | royalty-free, non transferable, non sublicensable, non exclusive, 73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and 74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 75 | maximum duration provided by applicable law or treaty (including future time 76 | extensions), (iii) in any current or future medium and for any number of 77 | copies, and (iv) for any purpose whatsoever, including without limitation 78 | commercial, advertising or promotional purposes (the “License”). The License 79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the 80 | Work. Should any part of the License for any reason be judged legally 81 | invalid or ineffective under applicable law, such partial invalidity or 82 | ineffectiveness shall not invalidate the remainder of the License, and in 83 | such case Affirmer hereby affirms that he or she will not (i) exercise any 84 | of his or her remaining Copyright and Related Rights in the Work or (ii) 85 | assert any associated claims and causes of action with respect to the Work, 86 | in either case contrary to Affirmer’s express Statement of Purpose. 87 | 88 | 4. Limitations and Disclaimers. 89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned, 90 | surrendered, licensed or otherwise affected by this document. 91 | 2. Affirmer offers the Work as-is and makes no representations or warranties 92 | of any kind concerning the Work, express, implied, statutory or 93 | otherwise, including without limitation warranties of title, 94 | merchantability, fitness for a particular purpose, non infringement, or 95 | the absence of latent or other defects, accuracy, or the present or 96 | absence of errors, whether or not discoverable, all to the greatest 97 | extent permissible under applicable law. 98 | 3. Affirmer disclaims responsibility for clearing rights of other persons 99 | that may apply to the Work or any use thereof, including without 100 | limitation any person’s Copyright and Related Rights in the Work. 101 | Further, Affirmer disclaims responsibility for obtaining any necessary 102 | consents, permissions or other rights required for any use of the Work. 103 | 4. Affirmer understands and acknowledges that Creative Commons is not a 104 | party to this document and has no duty or obligation with respect to this 105 | CC0 or use of the Work. 106 | 107 | For more information, please see 108 | http://creativecommons.org/publicdomain/zero/1.0/. 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
⚠️ PostCSS Focus Within was moved to @csstools/postcss-plugins. ⚠️
2 | Read the announcement
3 | 4 | # PostCSS Focus Within [PostCSS Logo][postcss] 5 | 6 | [npm version][npm-url] 7 | [CSS Standard Status][css-url] 8 | [Build Status][cli-url] 9 | [support chat][git-url] 10 | 11 | [PostCSS Focus Within] lets you use the `:focus-within` pseudo-class in CSS, 12 | following the [Selectors Level 4 specification]. 13 | 14 | It is the companion to the [focus-within polyfill]. 15 | 16 | [!['Can I use' table](https://caniuse.bitsofco.de/image/css-focus-within.png)](https://caniuse.com/#feat=css-focus-within) 17 | 18 | ```css 19 | .my-form-field:focus-within label { 20 | background-color: yellow; 21 | } 22 | 23 | /* becomes */ 24 | 25 | .my-form-field[focus-within] label { 26 | background-color: yellow; 27 | } 28 | 29 | .my-form-field:focus-within label { 30 | background-color: yellow; 31 | } 32 | ``` 33 | 34 | [PostCSS Focus Within] duplicates rules using the `:focus-within` pseudo-class 35 | with a `[focus-within]` attribute selector, the same selector used by the 36 | [focus-within polyfill]. This replacement selector can be changed using the 37 | `replaceWith` option. Also, the preservation of the original `:focus-within` 38 | rule can be disabled using the `preserve` option. 39 | 40 | ## Usage 41 | 42 | Add [PostCSS Focus Within] to your project: 43 | 44 | ```bash 45 | npm install postcss postcss-focus-within --save-dev 46 | ``` 47 | 48 | Use [PostCSS Focus Within] to process your CSS: 49 | 50 | ```js 51 | const postcssFocusWithin = require('postcss-focus-within'); 52 | 53 | postcssFocusWithin.process(YOUR_CSS /*, processOptions, pluginOptions */); 54 | ``` 55 | 56 | Or use it as a [PostCSS] plugin: 57 | 58 | ```js 59 | const postcss = require('postcss'); 60 | const postcssFocusWithin = require('postcss-focus-within'); 61 | 62 | postcss([ 63 | postcssFocusWithin(/* pluginOptions */) 64 | ]).process(YOUR_CSS /*, processOptions */); 65 | ``` 66 | 67 | [PostCSS Focus Within] runs in all Node environments, with special 68 | instructions for: 69 | 70 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | 71 | | --- | --- | --- | --- | --- | --- | 72 | 73 | ## Options 74 | 75 | ### preserve 76 | 77 | The `preserve` option defines whether the original selector should remain. By 78 | default, the original selector is preserved. 79 | 80 | ```js 81 | focusWithin({ preserve: false }); 82 | ``` 83 | 84 | ```css 85 | .my-form-field:focus-within label { 86 | background-color: yellow; 87 | } 88 | 89 | /* becomes */ 90 | 91 | .my-form-field[focus-within] label { 92 | background-color: yellow; 93 | } 94 | ``` 95 | 96 | ### replaceWith 97 | 98 | The `replaceWith` option defines the selector to replace `:focus-within`. By 99 | default, the replacement selector is `[focus-within]`. 100 | 101 | ```js 102 | focusWithin({ replaceWith: '.focus-within' }); 103 | ``` 104 | 105 | ```css 106 | .my-form-field:focus-within label { 107 | background-color: yellow; 108 | } 109 | 110 | /* becomes */ 111 | 112 | .my-form-field.focus-within label { 113 | background-color: yellow; 114 | } 115 | 116 | .my-form-field:focus-within label { 117 | background-color: yellow; 118 | } 119 | ``` 120 | 121 | [css-url]: https://cssdb.org/#focus-within-pseudo-class 122 | [cli-url]: https://github.com/csstools/postcss-focus-within/actions/workflows/test.yml?query=workflow/test 123 | [git-url]: https://gitter.im/postcss/postcss 124 | [npm-url]: https://www.npmjs.com/package/postcss-focus-within 125 | 126 | [focus-within polyfill]: https://github.com/jsxtools/focus-within 127 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss 128 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss 129 | [PostCSS]: https://github.com/postcss/postcss 130 | [PostCSS Focus Within]: https://github.com/csstools/postcss-focus-within 131 | [PostCSS Loader]: https://github.com/postcss/postcss-loader 132 | [Selectors Level 4 specification]: https://www.w3.org/TR/selectors-4/#the-focus-within-pseudo 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-focus-within", 3 | "version": "5.0.1", 4 | "description": "Use the :focus-within pseudo-selector in CSS", 5 | "author": "Jonathan Neal ", 6 | "license": "CC0-1.0", 7 | "repository": "csstools/postcss-focus-within", 8 | "homepage": "https://github.com/csstools/postcss-focus-within#readme", 9 | "bugs": "https://github.com/csstools/postcss-focus-within/issues", 10 | "main": "dist/index.cjs", 11 | "module": "dist/index.mjs", 12 | "types": "./dist/index.d.ts", 13 | "exports": { 14 | ".": { 15 | "import": "./dist/index.mjs", 16 | "require": "./dist/index.cjs", 17 | "types": "./dist/index.d.ts" 18 | } 19 | }, 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "build": "npx rollup -c .rollup.js", 25 | "build:watch": "npx rollup -c .rollup.js --watch", 26 | "lint": "npx eslint --cache src", 27 | "lint:fix": "npx eslint --cache --fix", 28 | "prepublishOnly": "npm test", 29 | "pretest": "npm install && npm run build", 30 | "test": "npm run lint && npm run tape", 31 | "tape": "npx postcss-tape" 32 | }, 33 | "engines": { 34 | "node": ">=12" 35 | }, 36 | "peerDependencies": { 37 | "postcss": "^8.3" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "7.15.5", 41 | "@babel/preset-env": "7.15.6", 42 | "eslint": "7.32.0", 43 | "postcss": "8.3.6", 44 | "postcss-tape": "6.0.1", 45 | "pre-commit": "1.2.2", 46 | "rollup": "2.57.0", 47 | "rollup-plugin-babel": "4.4.0", 48 | "rollup-plugin-copy": "^3.4.0" 49 | }, 50 | "babel": { 51 | "presets": [ 52 | [ 53 | "@babel/env", 54 | { 55 | "targets": "maintained node versions" 56 | } 57 | ] 58 | ] 59 | }, 60 | "eslintConfig": { 61 | "env": { 62 | "es6": true, 63 | "node": true 64 | }, 65 | "extends": "eslint:recommended", 66 | "parserOptions": { 67 | "ecmaVersion": 2020, 68 | "sourceType": "module" 69 | } 70 | }, 71 | "rollup": { 72 | "input": "src/index.js", 73 | "plugins": [ 74 | "rollup-plugin-babel", 75 | [ 76 | "rollup-plugin-copy", 77 | { 78 | "targets": [ 79 | { 80 | "src": "./src/index.d.ts", 81 | "dest": "./dist" 82 | } 83 | ] 84 | } 85 | ] 86 | ], 87 | "output": [ 88 | { 89 | "exports": "default", 90 | "file": "dist/index.cjs", 91 | "format": "cjs" 92 | }, 93 | { 94 | "file": "dist/index.mjs", 95 | "format": "esm" 96 | } 97 | ] 98 | }, 99 | "keywords": [ 100 | "postcss", 101 | "css", 102 | "postcss-plugin", 103 | "focus", 104 | "within", 105 | "polyfill", 106 | "pseudos", 107 | "selectors", 108 | "accessibility", 109 | "a11y", 110 | "descendants", 111 | "ancestors" 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | export type PluginOptions = { 2 | /** Defines whether the original selector should remain. */ 3 | preserve?: boolean 4 | /** Defines the selector to replace `:focus-within`. */ 5 | replaceWith?: string 6 | } 7 | 8 | export type Plugin = { 9 | (pluginOptions?: PluginOptions): { 10 | postcssPlugin: 'postcss-focus-within' 11 | } 12 | postcss: true 13 | } 14 | 15 | declare const plugin: Plugin 16 | 17 | export default plugin 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const selectorRegExp = /(? { 4 | const replaceWith = String(Object(opts).replaceWith || '[focus-within]'); 5 | const preserve = Boolean('preserve' in Object(opts) ? opts.preserve : true); 6 | 7 | return { 8 | postcssPlugin: 'postcss-focus-within', 9 | Rule: rule => { 10 | if (!selectorRegExp.test(rule.selector)) { 11 | return; 12 | } 13 | 14 | const selector = rule.selector.replace(selectorRegExp, ($0, $1) => `${replaceWith}${$1}`); 15 | 16 | // Check is the rule is processed yet 17 | if (preserve && rule.prev()?.selector === selector) { 18 | return; 19 | } 20 | 21 | const clone = rule.clone({ selector }); 22 | 23 | if (preserve) { 24 | rule.before(clone); 25 | } else { 26 | rule.replaceWith(clone); 27 | } 28 | } 29 | } 30 | }; 31 | 32 | plugin.postcss = true; 33 | 34 | export default plugin; 35 | -------------------------------------------------------------------------------- /test/basic.css: -------------------------------------------------------------------------------- 1 | :focus-within { 2 | order: 1; 3 | } 4 | 5 | :focus-within, 6 | :focus-within test, 7 | test :focus-within, 8 | test test:focus-within, 9 | test :focus-within test, 10 | test test:focus-within test, 11 | test :focus-within :focus-within test, 12 | test :matches(:focus-within) test, 13 | test :matches(:focus-within test) test, 14 | test :matches(test :focus-within) test, 15 | test :matches(test test:focus-within) test, 16 | test :matches(test :focus-within test) test, 17 | test :matches(test test:focus-within test) test, 18 | test :matches(test :focus-within :focus-within test) test { 19 | order: 2; 20 | } 21 | 22 | :ignore-focus-within, 23 | :focus-within-ignore, 24 | :ignorefocus-within, 25 | :focus-withinignore { 26 | order: 3; 27 | } 28 | 29 | .escaped\:focus-within, 30 | .escaped\:times\:two\:focus-within, 31 | .escaped\:focus-within:focus-within, 32 | .escaped\:times\:two:focus-within { 33 | order: 4; 34 | } 35 | -------------------------------------------------------------------------------- /test/basic.expect.css: -------------------------------------------------------------------------------- 1 | [focus-within] { 2 | order: 1; 3 | } 4 | 5 | :focus-within { 6 | order: 1; 7 | } 8 | 9 | [focus-within], 10 | [focus-within] test, 11 | test [focus-within], 12 | test test[focus-within], 13 | test [focus-within] test, 14 | test test[focus-within] test, 15 | test [focus-within] [focus-within] test, 16 | test :matches([focus-within]) test, 17 | test :matches([focus-within] test) test, 18 | test :matches(test [focus-within]) test, 19 | test :matches(test test[focus-within]) test, 20 | test :matches(test [focus-within] test) test, 21 | test :matches(test test[focus-within] test) test, 22 | test :matches(test [focus-within] [focus-within] test) test { 23 | order: 2; 24 | } 25 | 26 | :focus-within, 27 | :focus-within test, 28 | test :focus-within, 29 | test test:focus-within, 30 | test :focus-within test, 31 | test test:focus-within test, 32 | test :focus-within :focus-within test, 33 | test :matches(:focus-within) test, 34 | test :matches(:focus-within test) test, 35 | test :matches(test :focus-within) test, 36 | test :matches(test test:focus-within) test, 37 | test :matches(test :focus-within test) test, 38 | test :matches(test test:focus-within test) test, 39 | test :matches(test :focus-within :focus-within test) test { 40 | order: 2; 41 | } 42 | 43 | :ignore-focus-within, 44 | :focus-within-ignore, 45 | :ignorefocus-within, 46 | :focus-withinignore { 47 | order: 3; 48 | } 49 | 50 | .escaped\:focus-within, 51 | .escaped\:times\:two\:focus-within, 52 | .escaped\:focus-within[focus-within], 53 | .escaped\:times\:two[focus-within] { 54 | order: 4; 55 | } 56 | 57 | .escaped\:focus-within, 58 | .escaped\:times\:two\:focus-within, 59 | .escaped\:focus-within:focus-within, 60 | .escaped\:times\:two:focus-within { 61 | order: 4; 62 | } 63 | -------------------------------------------------------------------------------- /test/basic.preserve.expect.css: -------------------------------------------------------------------------------- 1 | [focus-within] { 2 | order: 1; 3 | } 4 | 5 | [focus-within], 6 | [focus-within] test, 7 | test [focus-within], 8 | test test[focus-within], 9 | test [focus-within] test, 10 | test test[focus-within] test, 11 | test [focus-within] [focus-within] test, 12 | test :matches([focus-within]) test, 13 | test :matches([focus-within] test) test, 14 | test :matches(test [focus-within]) test, 15 | test :matches(test test[focus-within]) test, 16 | test :matches(test [focus-within] test) test, 17 | test :matches(test test[focus-within] test) test, 18 | test :matches(test [focus-within] [focus-within] test) test { 19 | order: 2; 20 | } 21 | 22 | :ignore-focus-within, 23 | :focus-within-ignore, 24 | :ignorefocus-within, 25 | :focus-withinignore { 26 | order: 3; 27 | } 28 | 29 | .escaped\:focus-within, 30 | .escaped\:times\:two\:focus-within, 31 | .escaped\:focus-within[focus-within], 32 | .escaped\:times\:two[focus-within] { 33 | order: 4; 34 | } 35 | -------------------------------------------------------------------------------- /test/basic.replacewith.expect.css: -------------------------------------------------------------------------------- 1 | .focus-within { 2 | order: 1; 3 | } 4 | 5 | :focus-within { 6 | order: 1; 7 | } 8 | 9 | .focus-within, 10 | .focus-within test, 11 | test .focus-within, 12 | test test.focus-within, 13 | test .focus-within test, 14 | test test.focus-within test, 15 | test .focus-within .focus-within test, 16 | test :matches(.focus-within) test, 17 | test :matches(.focus-within test) test, 18 | test :matches(test .focus-within) test, 19 | test :matches(test test.focus-within) test, 20 | test :matches(test .focus-within test) test, 21 | test :matches(test test.focus-within test) test, 22 | test :matches(test .focus-within .focus-within test) test { 23 | order: 2; 24 | } 25 | 26 | :focus-within, 27 | :focus-within test, 28 | test :focus-within, 29 | test test:focus-within, 30 | test :focus-within test, 31 | test test:focus-within test, 32 | test :focus-within :focus-within test, 33 | test :matches(:focus-within) test, 34 | test :matches(:focus-within test) test, 35 | test :matches(test :focus-within) test, 36 | test :matches(test test:focus-within) test, 37 | test :matches(test :focus-within test) test, 38 | test :matches(test test:focus-within test) test, 39 | test :matches(test :focus-within :focus-within test) test { 40 | order: 2; 41 | } 42 | 43 | :ignore-focus-within, 44 | :focus-within-ignore, 45 | :ignorefocus-within, 46 | :focus-withinignore { 47 | order: 3; 48 | } 49 | 50 | .escaped\:focus-within, 51 | .escaped\:times\:two\:focus-within, 52 | .escaped\:focus-within.focus-within, 53 | .escaped\:times\:two.focus-within { 54 | order: 4; 55 | } 56 | 57 | .escaped\:focus-within, 58 | .escaped\:times\:two\:focus-within, 59 | .escaped\:focus-within:focus-within, 60 | .escaped\:times\:two:focus-within { 61 | order: 4; 62 | } 63 | --------------------------------------------------------------------------------