From ea3662051938d75dd4532e43c3f0bbe3c5c88dcc Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 14 Jun 2022 17:58:13 +0200 Subject: [PATCH 01/11] css-has-pseudo --- package-lock.json | 11 +- plugins/css-has-pseudo/.gitignore | 10 +- plugins/css-has-pseudo/.tape.mjs | 58 +- plugins/css-has-pseudo/INSTALL.md | 18 +- plugins/css-has-pseudo/README-BROWSER.md | 67 - plugins/css-has-pseudo/README-POSTCSS.md | 91 -- plugins/css-has-pseudo/README.md | 116 +- plugins/css-has-pseudo/docs/README.md | 52 + plugins/css-has-pseudo/package.json | 41 +- plugins/css-has-pseudo/src/browser.js | 315 +++-- plugins/css-has-pseudo/src/cli.js | 15 - plugins/css-has-pseudo/src/encode/decode.mjs | 17 + plugins/css-has-pseudo/src/encode/encode.mjs | 20 + plugins/css-has-pseudo/src/encode/extract.mjs | 91 ++ plugins/css-has-pseudo/src/encode/test.mjs | 155 +++ plugins/css-has-pseudo/src/index.js | 156 --- plugins/css-has-pseudo/src/index.ts | 120 ++ plugins/css-has-pseudo/test/_browser.html | 1189 +++++++++++++++++ plugins/css-has-pseudo/test/_browser.mjs | 83 ++ plugins/css-has-pseudo/test/basic.css | 124 +- plugins/css-has-pseudo/test/basic.expect.css | 247 +++- .../test/basic.preserve.expect.css | 134 +- ...basic.specificity-matching-name.expect.css | 275 ++++ plugins/css-has-pseudo/test/browser.css | 184 +++ .../css-has-pseudo/test/browser.expect.css | 184 +++ .../css-has-pseudo/test/examples/example.css | 3 + .../test/examples/example.expect.css | 6 + .../examples/example.preserve-true.expect.css | 6 + .../test/generated-selector-cases.expect.css | 446 +++---- .../plugin-order-logical.after.expect.css | 6 + ...in-order-logical.after.preserve.expect.css | 57 + .../plugin-order-logical.before.expect.css | 6 + ...n-order-logical.before.preserve.expect.css | 57 + .../test/plugin-order-logical.css | 3 + .../plugin-order-nesting.after.expect.css | 12 + ...in-order-nesting.after.preserve.expect.css | 24 + .../plugin-order-nesting.before.expect.css | 12 + ...n-order-nesting.before.preserve.expect.css | 24 + .../test/plugin-order-nesting.css | 17 + plugins/css-has-pseudo/tsconfig.json | 9 + 40 files changed, 3681 insertions(+), 780 deletions(-) delete mode 100644 plugins/css-has-pseudo/README-BROWSER.md delete mode 100644 plugins/css-has-pseudo/README-POSTCSS.md create mode 100644 plugins/css-has-pseudo/docs/README.md delete mode 100644 plugins/css-has-pseudo/src/cli.js create mode 100644 plugins/css-has-pseudo/src/encode/decode.mjs create mode 100644 plugins/css-has-pseudo/src/encode/encode.mjs create mode 100644 plugins/css-has-pseudo/src/encode/extract.mjs create mode 100644 plugins/css-has-pseudo/src/encode/test.mjs delete mode 100644 plugins/css-has-pseudo/src/index.js create mode 100644 plugins/css-has-pseudo/src/index.ts create mode 100644 plugins/css-has-pseudo/test/_browser.html create mode 100644 plugins/css-has-pseudo/test/_browser.mjs create mode 100644 plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css create mode 100644 plugins/css-has-pseudo/test/browser.css create mode 100644 plugins/css-has-pseudo/test/browser.expect.css create mode 100644 plugins/css-has-pseudo/test/examples/example.css create mode 100644 plugins/css-has-pseudo/test/examples/example.expect.css create mode 100644 plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-logical.after.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-logical.before.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-logical.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-nesting.after.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-nesting.after.preserve.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-nesting.before.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-nesting.before.preserve.expect.css create mode 100644 plugins/css-has-pseudo/test/plugin-order-nesting.css create mode 100644 plugins/css-has-pseudo/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 72c128c17..8ab6a4b47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6775,10 +6775,12 @@ "version": "3.0.4", "license": "CC0-1.0", "dependencies": { + "@csstools/selector-specificity": "^2.0.1", "postcss-selector-parser": "^6.0.10" }, - "bin": { - "css-has-pseudo": "dist/cli.cjs" + "devDependencies": { + "@mrhenry/core-web": "^0.7.2", + "puppeteer": "^13.6.0" }, "engines": { "node": "^12 || ^14 || >=16" @@ -9747,7 +9749,10 @@ "css-has-pseudo": { "version": "file:plugins/css-has-pseudo", "requires": { - "postcss-selector-parser": "^6.0.10" + "@csstools/selector-specificity": "^2.0.1", + "@mrhenry/core-web": "^0.7.2", + "postcss-selector-parser": "^6.0.10", + "puppeteer": "^13.6.0" } }, "css-prefers-color-scheme": { diff --git a/plugins/css-has-pseudo/.gitignore b/plugins/css-has-pseudo/.gitignore index 3559a9367..7172b04f1 100644 --- a/plugins/css-has-pseudo/.gitignore +++ b/plugins/css-has-pseudo/.gitignore @@ -1,14 +1,6 @@ node_modules -dist package-lock.json yarn.lock -browser.js -!src/browser.js -*.log* *.result.css *.result.css.map -!.editorconfig -!.gitignore -!.rollup.js -!.tape.js -!.travis.yml +dist/* diff --git a/plugins/css-has-pseudo/.tape.mjs b/plugins/css-has-pseudo/.tape.mjs index 77aab682e..ca4fab403 100644 --- a/plugins/css-has-pseudo/.tape.mjs +++ b/plugins/css-has-pseudo/.tape.mjs @@ -1,5 +1,8 @@ import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'css-has-pseudo'; +import postcssLogical from 'postcss-logical'; +import postcssNesting from 'postcss-nesting'; +import postcssDirPseudoClass from 'postcss-dir-pseudo-class'; postcssTape(plugin)({ 'basic': { @@ -11,6 +14,21 @@ postcssTape(plugin)({ preserve: false } }, + 'basic:specificity-matching-name': { + message: 'supports { specificityMatchingName: "other-thing-that-does-not-exist" } usage', + options: { + specificityMatchingName: 'other-thing-that-does-not-exist' + } + }, + 'examples/example': { + message: 'minimal example', + }, + 'examples/example:preserve-true': { + message: 'minimal example', + options: { + preserve: true + } + }, 'generated-selector-cases': { message: 'correctly handles generated cases', warnings: 1, @@ -18,4 +36,42 @@ postcssTape(plugin)({ preserve: false } }, -}) + 'browser': { + message: 'prepare CSS for chrome test', + options: { + preserve: false + } + }, + 'plugin-order-logical:before': { + message: 'works with other plugins that modify selectors', + plugins: [postcssLogical({ preserve: false }), postcssDirPseudoClass({ preserve: false }), plugin({ preserve: false })], + }, + 'plugin-order-logical:after': { + message: 'works with other plugins that modify selectors', + plugins: [plugin({ preserve: false }), postcssLogical({ preserve: false }), postcssDirPseudoClass({ preserve: false })], + }, + 'plugin-order-logical:before:preserve': { + message: 'works with other plugins that modify selectors', + plugins: [postcssLogical({ preserve: true }), postcssDirPseudoClass({ preserve: true }), plugin({ preserve: true })], + }, + 'plugin-order-logical:after:preserve': { + message: 'works with other plugins that modify selectors', + plugins: [plugin({ preserve: true }), postcssLogical({ preserve: true }), postcssDirPseudoClass({ preserve: true })], + }, + 'plugin-order-nesting:before': { + message: 'works with other plugins that modify selectors', + plugins: [postcssNesting({ preserve: false }), plugin({ preserve: false })], + }, + 'plugin-order-nesting:after': { + message: 'works with other plugins that modify selectors', + plugins: [postcssNesting({ preserve: false }), plugin({ preserve: false })], + }, + 'plugin-order-nesting:before:preserve': { + message: 'works with other plugins that modify selectors', + plugins: [plugin({ preserve: true }), postcssNesting({ preserve: true })], + }, + 'plugin-order-nesting:after:preserve': { + message: 'works with other plugins that modify selectors', + plugins: [plugin({ preserve: true }), postcssNesting({ preserve: true })], + } +}); diff --git a/plugins/css-has-pseudo/INSTALL.md b/plugins/css-has-pseudo/INSTALL.md index 49780082c..67915df26 100644 --- a/plugins/css-has-pseudo/INSTALL.md +++ b/plugins/css-has-pseudo/INSTALL.md @@ -1,13 +1,13 @@ -# Installing CSS Has Pseudo +# Installing PostCSS Has Pseudo -[CSS Has Pseudo] runs in all Node environments, with special instructions for: +[PostCSS Has Pseudo] runs in all Node environments, with special instructions for: | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | | --- | --- | --- | --- | --- | --- | ## Node -Add [CSS Has Pseudo] to your project: +Add [PostCSS Has Pseudo] to your project: ```bash npm install postcss css-has-pseudo --save-dev @@ -32,7 +32,7 @@ Add [PostCSS CLI] to your project: npm install postcss-cli css-has-pseudo --save-dev ``` -Use [CSS Has Pseudo] in your `postcss.config.js` configuration file: +Use [PostCSS Has Pseudo] in your `postcss.config.js` configuration file: ```js const postcssHasPseudo = require('css-has-pseudo'); @@ -54,7 +54,7 @@ Add [PostCSS Loader] to your project: npm install postcss-loader css-has-pseudo --save-dev ``` -Use [CSS Has Pseudo] in your Webpack configuration: +Use [PostCSS Has Pseudo] in your Webpack configuration: ```js module.exports = { @@ -98,7 +98,7 @@ Add [React App Rewired] and [React App Rewire PostCSS] to your project: npm install react-app-rewired react-app-rewire-postcss css-has-pseudo --save-dev ``` -Use [React App Rewire PostCSS] and [CSS Has Pseudo] in your +Use [React App Rewire PostCSS] and [PostCSS Has Pseudo] in your `config-overrides.js` file: ```js @@ -120,7 +120,7 @@ Add [Gulp PostCSS] to your project: npm install gulp-postcss css-has-pseudo --save-dev ``` -Use [CSS Has Pseudo] in your Gulpfile: +Use [PostCSS Has Pseudo] in your Gulpfile: ```js const postcss = require('gulp-postcss'); @@ -145,7 +145,7 @@ Add [Grunt PostCSS] to your project: npm install grunt-postcss css-has-pseudo --save-dev ``` -Use [CSS Has Pseudo] in your Gruntfile: +Use [PostCSS Has Pseudo] in your Gruntfile: ```js const postcssHasPseudo = require('css-has-pseudo'); @@ -171,6 +171,6 @@ grunt.initConfig({ [PostCSS]: https://github.com/postcss/postcss [PostCSS CLI]: https://github.com/postcss/postcss-cli [PostCSS Loader]: https://github.com/postcss/postcss-loader -[CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo +[PostCSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss [React App Rewired]: https://github.com/timarney/react-app-rewired diff --git a/plugins/css-has-pseudo/README-BROWSER.md b/plugins/css-has-pseudo/README-BROWSER.md deleted file mode 100644 index 3af212ef1..000000000 --- a/plugins/css-has-pseudo/README-BROWSER.md +++ /dev/null @@ -1,67 +0,0 @@ -# CSS Has Pseudo for Browsers [][CSS Has Pseudo] - -[![NPM Version][npm-img]][npm-url] -[Discord][discord] - -[CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -```css -input { - /* style an input */ -} - -body[\:has\(\:focus\)] { - /* style an input without a value */ -} -``` - -## Usage - -Add [CSS Has Pseudo] to your build tool: - -```bash -npm install css-has-pseudo -``` - -Then include and initialize it on your document: - -```js -const cssHasPseudo = require('css-has-pseudo/browser'); - -cssHasPseudo(document); -``` - -```html - - - -``` - -## Dependencies - -Web API's: - -- [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) -- [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) -- [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) with support for post CSS 2.1 selectors - -ECMA Script: - -- `Array.prototype.filter` -- `Array.prototype.forEach` -- `Array.prototype.indexOf` -- `Array.prototype.join` -- `Array.prototype.map` -- `Array.prototype.splice` -- `RegExp.prototype.exec` -- `String.prototype.match` -- `String.prototype.replace` -- `String.prototype.split` - -[discord]: https://discord.gg/bUadyRwkJS -[npm-img]: https://img.shields.io/npm/v/css-has-pseudo.svg -[npm-url]: https://www.npmjs.com/package/css-has-pseudo - -[CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo -[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has diff --git a/plugins/css-has-pseudo/README-POSTCSS.md b/plugins/css-has-pseudo/README-POSTCSS.md deleted file mode 100644 index 6f50c2dc5..000000000 --- a/plugins/css-has-pseudo/README-POSTCSS.md +++ /dev/null @@ -1,91 +0,0 @@ -# CSS Has Pseudo for PostCSS [][CSS Has Pseudo] - -[![NPM Version][npm-img]][npm-url] -[Discord][discord] - -[CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -```css -body:has(:focus) { - background-color: yellow; -} - -/* becomes */ - -body[\:has\(\:focus\)] { - background-color: yellow; -} - -body:has(:focus) { - background-color: yellow; -} -``` - -[CSS Has Pseudo] duplicates rules using the `:has` pseudo-class with a `[has]` -attribute selector. The preservation of the original `:has` rule can be -disabled using the `preserve` option. - -## Usage - -Add [CSS Has Pseudo] to your project: - -```bash -npm install css-has-pseudo --save-dev -``` - -Use [CSS Has Pseudo] to process your CSS: - -```js -const postcssHasPseudo = require('css-has-pseudo'); - -postcssHasPseudo.process(YOUR_CSS /*, processOptions, pluginOptions */); -``` - -Or use it as a [PostCSS] plugin: - -```js -const postcss = require('postcss'); -const postcssHasPseudo = require('css-has-pseudo'); - -postcss([ - postcssHasPseudo(/* pluginOptions */) -]).process(YOUR_CSS /*, processOptions */); -``` - -[CSS Has Pseudo] runs in all Node environments, with special -instructions for: - -| [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) | -| --- | --- | --- | --- | --- | --- | - -## Options - -### preserve - -The `preserve` option defines whether the original selector should remain. By -default, the original selector is preserved. - -```js -hasPseudo({ preserve: false }); -``` - -```css -body:has(:focus) { - background-color: yellow; -} - -/* becomes */ - -body[\:has\(\:focus\)] { - background-color: yellow; -} -``` - -[discord]: https://discord.gg/bUadyRwkJS -[npm-img]: https://img.shields.io/npm/v/css-has-pseudo.svg -[npm-url]: https://www.npmjs.com/package/css-has-pseudo - -[PostCSS]: https://github.com/postcss/postcss -[CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo -[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has-pseudo diff --git a/plugins/css-has-pseudo/README.md b/plugins/css-has-pseudo/README.md index 0125e3fd5..62ed16a75 100644 --- a/plugins/css-has-pseudo/README.md +++ b/plugins/css-has-pseudo/README.md @@ -1,105 +1,83 @@ -# CSS Has Pseudo [][CSS Has Pseudo] +# PostCSS Has Pseudo [PostCSS Logo][postcss] -[![NPM Version][npm-img]][npm-url] -[Discord][discord] +[npm version][npm-url] [CSS Standard Status][css-url] [Build Status][cli-url] [Discord][discord] -[CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -[!['Can I use' table](https://caniuse.bitsofco.de/image/css-has.png)](https://caniuse.com/#feat=css-has) - -```css -a:has(> img) { - /* style links that contain an image */ -} +[PostCSS Has Pseudo] lets you easily create new plugins following some [CSS Specification]. +```pcss h1:has(+ p) { - /* style level 1 headings that are followed by a paragraph */ + margin-bottom: 1.5rem; } -section:not(:has(h1, h2, h3, h4, h5, h6)) { - /* style sections that don’t contain any heading elements */ -} +/* becomes */ -body:has(:focus) { - /* style the body if it contains a focused element */ +[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { + margin-bottom: 1.5rem; +} +h1:has(+ p) { + margin-bottom: 1.5rem; } ``` ## Usage -From the command line, transform CSS files that use `:has` selectors: +Add [PostCSS Has Pseudo] to your project: ```bash -npx css-has-pseudo SOURCE.css --output TRANSFORMED.css +npm install postcss css-has-pseudo --save-dev ``` -Next, use your transformed CSS with this script: +Use it as a [PostCSS] plugin: -```html - - - -``` +```js +const postcss = require('postcss'); +const postcssHasPseudo = require('css-has-pseudo'); -⚠️ Please use a versioned url, like this : `https://unpkg.com/css-has-pseudo@3.0.0/dist/browser-global.js` -Without the version, you might unexpectedly get a new major version of the library with breaking changes. +postcss([ + postcssHasPseudo(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` -⚠️ If you were using an older version via a CDN, please update the entire url. -The old URL will no longer work in a future release. +[PostCSS Has Pseudo] runs in all Node environments, with special +instructions for: -That’s it. The script is 765 bytes and works in most browser versions, including -Internet Explorer 11. With a [Mutation Observer polyfill], the script will work -down to Internet Explorer 9. +| [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) | +| --- | --- | --- | --- | --- | --- | -See [README BROWSER](README-BROWSER.md) for more information. +## Options -## How it works +### preserve -The [PostCSS plugin](README-POSTCSS.md) clones rules containing `:has`, -replacing them with an alternative `[:has]` selector. +The `preserve` option determines whether the original notation +is preserved. By default, it is not preserved. -```css -body:has(:focus) { - background-color: yellow; -} +```js +postcssHasPseudo({ preserve: true }) +``` -section:not(:has(h1, h2, h3, h4, h5, h6)) { - background-color: gray; +```pcss +h1:has(+ p) { + margin-bottom: 1.5rem; } /* becomes */ -body[\:has\(\:focus\)] { - background-color: yellow; -} - -body:has(:focus) { - background-color: yellow; -} - -section[\:not-has\(h1\,\%20h2\,\%20h3\,\%20h4\,\%20h5\,\%20h6\)] { - background-color: gray; +[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { + margin-bottom: 1.5rem; } - -section:not(:has(h1, h2, h3, h4, h5, h6)) { - background-color: gray; +h1:has(+ p) { + margin-bottom: 1.5rem; } ``` -Next, the [JavaScript library](README-BROWSER.md) adds a `[:has]` attribute to -elements otherwise matching `:has` natively. - -```html - - - -``` - +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test +[css-url]: https://cssdb.org/#has-pseudo-class [discord]: https://discord.gg/bUadyRwkJS -[npm-img]: https://img.shields.io/npm/v/css-has-pseudo.svg [npm-url]: https://www.npmjs.com/package/css-has-pseudo -[CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo -[Mutation Observer polyfill]: https://github.com/webmodules/mutation-observer -[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has-pseudo +[Gulp PostCSS]: https://github.com/postcss/gulp-postcss +[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Loader]: https://github.com/postcss/postcss-loader +[PostCSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo +[CSS Specification]: https://www.w3.org/TR/selectors-4/#has-pseudo diff --git a/plugins/css-has-pseudo/docs/README.md b/plugins/css-has-pseudo/docs/README.md new file mode 100644 index 000000000..ffae28178 --- /dev/null +++ b/plugins/css-has-pseudo/docs/README.md @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + +
+ +[] lets you easily create new plugins following some [CSS Specification]. + +```pcss + + +/* becomes */ + + +``` + + + + + +## Options + +### preserve + +The `preserve` option determines whether the original notation +is preserved. By default, it is not preserved. + +```js +({ preserve: true }) +``` + +```pcss + + +/* becomes */ + + +``` + + +[CSS Specification]: diff --git a/plugins/css-has-pseudo/package.json b/plugins/css-has-pseudo/package.json index 2a475a6d7..bf24a02f7 100644 --- a/plugins/css-has-pseudo/package.json +++ b/plugins/css-has-pseudo/package.json @@ -2,7 +2,21 @@ "name": "css-has-pseudo", "description": "Style elements relative to other elements in CSS", "version": "3.0.4", - "author": "Jonathan Neal ", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke", + "email": "romainmenke@gmail.com" + }, + { + "name": "Jonathan Neal", + "email": "jonathantneal@hotmail.com" + } + ], "license": "CC0-1.0", "funding": { "type": "opencollective", @@ -13,9 +27,7 @@ }, "main": "dist/index.cjs", "module": "dist/index.mjs", - "bin": { - "css-has-pseudo": "dist/cli.cjs" - }, + "types": "dist/index.d.ts", "exports": { ".": { "import": "./dist/index.mjs", @@ -35,27 +47,32 @@ "CHANGELOG.md", "LICENSE.md", "README.md", - "browser.js", "dist" ], "dependencies": { + "@csstools/selector-specificity": "^2.0.1", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" }, + "devDependencies": { + "@mrhenry/core-web": "^0.7.2", + "puppeteer": "^13.6.0" + }, "scripts": { - "build": "rollup -c ../../rollup/default.js && npm run copy-browser-scripts-to-old-location", + "build": "rollup -c ../../rollup/default.js", "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", - "copy-browser-scripts-to-old-location": "node -e \"fs.copyFileSync('./dist/browser-global.js', './browser.js')\"", - "docs": "node ../../.github/bin/generate-docs/install.mjs", + "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs", "lint": "npm run lint:eslint && npm run lint:package-json", "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", "lint:package-json": "node ../../.github/bin/format-package-json.mjs", "prepublishOnly": "npm run clean && npm run build && npm run test", - "test": "node .tape.mjs && npm run test:exports", + "test": "node .tape.mjs && npm run test:unit && npm run test:exports", + "test:browser": "node ./test/_browser.mjs", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", - "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", + "test:unit": "node ./src/encode/test.mjs" }, "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo#readme", "repository": { @@ -78,8 +95,10 @@ "selector" ], "csstools": { + "cssdbId": "has-pseudo-class", "exportName": "postcssHasPseudo", - "humanReadableName": "CSS Has Pseudo" + "humanReadableName": "PostCSS Has Pseudo", + "specUrl": "https://www.w3.org/TR/selectors-4/#has-pseudo" }, "volta": { "extends": "../../package.json" diff --git a/plugins/css-has-pseudo/src/browser.js b/plugins/css-has-pseudo/src/browser.js index 7fe3f28c8..398156d8c 100644 --- a/plugins/css-has-pseudo/src/browser.js +++ b/plugins/css-has-pseudo/src/browser.js @@ -1,5 +1,54 @@ -/* global MutationObserver,requestAnimationFrame */ -export default function cssHasPseudo(document) { +/* global MutationObserver,requestAnimationFrame,cancelAnimationFrame,self,HTMLElement */ + +import '@mrhenry/core-web/modules/~element-qsa-has.js'; +import extractEncodedSelectors from './encode/extract.mjs'; +import encodeCSS from './encode/encode.mjs'; + +export default function cssHasPseudo(document, options) { + // OPTIONS + { + if (!options) { + options = {}; + } + + options = { + hover: (!!options.hover) || false, + debug: (!!options.debug) || false, + observedAttributes: options.observedAttributes || [], + forcePolyfill: (!!options.forcePolyfill) || false, + }; + + if (!options.forcePolyfill) { + try { + // Chrome does not support forgiving selector lists in :has() + document.querySelector(':has(*, :does-not-exist, > *)'); + + // Safari incorrectly returns the html element with this query + if (!document.querySelector(':has(:scope *)')) { + // Native support detected. + // Doing early return. + return; + } + + // fallthrough to polyfill + } catch (_) { + // fallthrough to polyfill + } + } + + if (!Array.isArray(options.observedAttributes)) { + options.observedAttributes = []; + } + + options.observedAttributes = options.observedAttributes.filter((x) => { + return (typeof x === 'string'); + }); + + // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes + // `data-*` and `style` were omitted + options.observedAttributes = options.observedAttributes.concat(['accept', 'accept-charset', 'accesskey', 'action', 'align', 'allow', 'alt', 'async', 'autocapitalize', 'autocomplete', 'autofocus', 'autoplay', 'buffered', 'capture', 'challenge', 'charset', 'checked', 'cite', 'class', 'code', 'codebase', 'cols', 'colspan', 'content', 'contenteditable', 'contextmenu', 'controls', 'coords', 'crossorigin', 'csp', 'data', 'datetime', 'decoding', 'default', 'defer', 'dir', 'dirname', 'disabled', 'download', 'draggable', 'enctype', 'enterkeyhint', 'for', 'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'headers', 'hidden', 'high', 'href', 'hreflang', 'http-equiv', 'icon', 'id', 'importance', 'integrity', 'intrinsicsize', 'inputmode', 'ismap', 'itemprop', 'keytype', 'kind', 'label', 'lang', 'language', 'list', 'loop', 'low', 'manifest', 'max', 'maxlength', 'minlength', 'media', 'method', 'min', 'multiple', 'muted', 'name', 'novalidate', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'radiogroup', 'readonly', 'referrerpolicy', 'rel', 'required', 'reversed', 'rows', 'rowspan', 'sandbox', 'scope', 'scoped', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src', 'srcdoc', 'srclang', 'srcset', 'start', 'step', 'summary', 'tabindex', 'target', 'title', 'translate', 'type', 'usemap', 'value', 'width', 'wrap']); + } + const observedItems = []; // document.createAttribute() doesn't support `:` in the name. innerHTML does @@ -7,127 +56,207 @@ export default function cssHasPseudo(document) { // walk all stylesheets to collect observed css rules [].forEach.call(document.styleSheets, walkStyleSheet); - transformObservedItems(); + transformObservedItemsThrottled(); // observe DOM modifications that affect selectors - const mutationObserver = new MutationObserver(mutationsList => { - mutationsList.forEach(mutation => { - [].forEach.call(mutation.addedNodes || [], node => { - // walk stylesheets to collect observed css rules - if (node.nodeType === 1 && node.sheet) { - walkStyleSheet(node.sheet); + if ('MutationObserver' in self) { + const mutationObserver = new MutationObserver((mutationsList) => { + mutationsList.forEach(mutation => { + [].forEach.call(mutation.addedNodes || [], node => { + // walk stylesheets to collect observed css rules + if (node.nodeType === 1 && node.sheet) { + walkStyleSheet(node.sheet); + } + }); + + // transform observed css rules + cleanupObservedCssRules(); + transformObservedItemsThrottled(); + }); + }); + + mutationObserver.observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: options.observedAttributes }); + } + + // observe DOM events that affect pseudo-selectors + document.addEventListener('focus', transformObservedItemsThrottled, true); + document.addEventListener('blur', transformObservedItemsThrottled, true); + document.addEventListener('input', transformObservedItemsThrottled); + document.addEventListener('change', transformObservedItemsThrottled, true); + + if (options.hover) { + if ('onpointerenter' in document) { + document.addEventListener('pointerenter', transformObservedItemsThrottled, true); + document.addEventListener('pointerleave', transformObservedItemsThrottled, true); + } else { + document.addEventListener('mouseover', transformObservedItemsThrottled, true); + document.addEventListener('mouseout', transformObservedItemsThrottled, true); + } + } + + // observe Javascript setters that effect pseudo-selectors + if ('defineProperty' in Object && 'getOwnPropertyDescriptor' in Object && 'hasOwnProperty' in Object) { + try { + // eslint-disable-next-line no-inner-declarations + function observeProperty(proto, property) { + // eslint-disable-next-line no-prototype-builtins + if (proto.hasOwnProperty(property)) { + const descriptor = Object.getOwnPropertyDescriptor(proto, property); + if (descriptor && descriptor.configurable && 'set' in descriptor) { + Object.defineProperty(proto, property, { + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + get: function () { + return descriptor.get.apply(this, arguments); + }, + set: function () { + descriptor.set.apply(this, arguments); + + try { + transformObservedItemsThrottled(); + } catch (_) { + // should never happen as there is an inner try/catch + // but just in case + } + }, + }); + } } + } + + if ('HTMLElement' in self && HTMLElement.prototype) { + observeProperty(HTMLElement.prototype, 'disabled'); + } + + // Not all of these elements have all of these properties. + // But the code above checks if they exist first. + ['checked', 'selected', 'readOnly', 'required'].forEach((property) => { + [ + 'HTMLButtonElement', + 'HTMLFieldSetElement', + 'HTMLInputElement', + 'HTMLMeterElement', + 'HTMLOptGroupElement', + 'HTMLOptionElement', + 'HTMLOutputElement', + 'HTMLProgressElement', + 'HTMLSelectElement', + 'HTMLTextAreaElement', + ].forEach((elementName) => { + if (elementName in self && self[elementName].prototype) { + observeProperty(self[elementName].prototype, property); + } + }); }); + } catch (e) { + if (options.debug) { + console.error(e); + } + } + } - // transform observed css rules - cleanupObservedCssRules(); + let transformObservedItemsThrottledBusy = false; + function transformObservedItemsThrottled() { + if (transformObservedItemsThrottledBusy) { + cancelAnimationFrame(transformObservedItemsThrottledBusy); + } + + transformObservedItemsThrottledBusy = requestAnimationFrame(() => { transformObservedItems(); }); - }); + } - mutationObserver.observe(document, { childList: true, subtree: true }); + // transform observed css rules + function transformObservedItems() { + observedItems.forEach((item) => { + const nodes = []; - // observe DOM events that affect pseudo-selectors - document.addEventListener('focus', transformObservedItems, true); - document.addEventListener('blur', transformObservedItems, true); - document.addEventListener('input', transformObservedItems); + let matches = []; + try { + matches = document.querySelectorAll(item.selector); + } catch (e) { + if (options.debug) { + console.error(e); + } + return; + } - // transform observed css rules - function transformObservedItems () { - requestAnimationFrame(() => { - observedItems.forEach( - item => { - const nodes = []; - - [].forEach.call( - document.querySelectorAll(item.scopeSelector), - element => { - const nthChild = [].indexOf.call(element.parentNode.children, element) + 1; - const relativeSelectors = item.relativeSelectors.map( - relativeSelector => item.scopeSelector + ':nth-child(' + nthChild + ') ' + relativeSelector, - ).join(); - - // find any relative :has element from the :scope element - const relativeElement = element.parentNode.querySelector(relativeSelectors); - - const shouldElementMatch = item.isNot ? !relativeElement : relativeElement; - - if (shouldElementMatch) { - // memorize the node - nodes.push(element); - - // set an attribute with an irregular attribute name - // document.createAttribute() doesn't support special characters - attributeElement.innerHTML = ''; - - element.setAttributeNode(attributeElement.children[0].attributes[0].cloneNode()); - - // trigger a style refresh in IE and Edge - document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; - } - }, - ); - - // remove the encoded attribute from all nodes that no longer match them - item.nodes.forEach(node => { - if (nodes.indexOf(node) === -1) { - node.removeAttribute(item.attributeName); - - // trigger a style refresh in IE and Edge - document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; - } - }); + [].forEach.call(matches, (element) => { + // memorize the node + nodes.push(element); + + // set an attribute with an irregular attribute name + // document.createAttribute() doesn't support special characters + attributeElement.innerHTML = ''; + + element.setAttributeNode(attributeElement.children[0].attributes[0].cloneNode()); + + // trigger a style refresh in IE and Edge + document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; + }); + + // remove the encoded attribute from all nodes that no longer match them + item.nodes.forEach(node => { + if (nodes.indexOf(node) === -1) { + node.removeAttribute(item.attributeName); + + // trigger a style refresh in IE and Edge + document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; + } + }); - // update the - item.nodes = nodes; - }, - ); + // update the + item.nodes = nodes; }); } // remove any observed cssrules that no longer apply - function cleanupObservedCssRules () { + function cleanupObservedCssRules() { [].push.apply( observedItems, - observedItems.splice(0).filter( - item => item.rule.parentStyleSheet && + observedItems.splice(0).filter((item) => { + return item.rule.parentStyleSheet && item.rule.parentStyleSheet.ownerNode && - document.documentElement.contains(item.rule.parentStyleSheet.ownerNode), - ), + document.documentElement.contains(item.rule.parentStyleSheet.ownerNode); + }), ); } // walk a stylesheet to collect observed css rules - function walkStyleSheet (styleSheet) { + function walkStyleSheet(styleSheet) { try { // walk a css rule to collect observed css rules - [].forEach.call(styleSheet.cssRules || [], rule => { + [].forEach.call(styleSheet.cssRules || [], (rule) => { if (rule.selectorText) { - // decode the selector text in all browsers to: - // [1] = :scope, [2] = :not(:has), [3] = :has relative, [4] = :scope relative - const selectors = decodeURIComponent(rule.selectorText.replace(/\\(.)/g, '$1')).match(/^(.*?)\[:(not-)?has\((.+?)\)\](.*?)$/); - - if (selectors) { - const attributeName = ':' + (selectors[2] ? 'not-' : '') + 'has(' + - // encode a :has() pseudo selector as an attribute name - encodeURIComponent(selectors[3]).replace(/%3A/g, ':').replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%2C/g, ',') + - ')'; - - observedItems.push({ - rule, - scopeSelector: selectors[1], - isNot: selectors[2], - relativeSelectors: selectors[3].split(/\s*,\s*/), - attributeName, - nodes: [], - }); + try { + // decode the selector text in all browsers to: + const hasSelectors = extractEncodedSelectors(rule.selectorText.toString()); + if (hasSelectors.length === 0) { + return; + } + + for (let i = 0; i < hasSelectors.length; i++) { + const hasSelector = hasSelectors[i]; + observedItems.push({ + rule: rule, + selector: hasSelector, + attributeName: encodeCSS(hasSelector), + nodes: [], + }); + } + } catch (e) { + if (options.debug) { + console.error(e); + } } } else { walkStyleSheet(rule); } }); - } catch (error) { - /* do nothing and continue */ + } catch (e) { + if (options.debug) { + console.error(e); + } } } } diff --git a/plugins/css-has-pseudo/src/cli.js b/plugins/css-has-pseudo/src/cli.js deleted file mode 100644 index db0162d30..000000000 --- a/plugins/css-has-pseudo/src/cli.js +++ /dev/null @@ -1,15 +0,0 @@ -import plugin from './index'; -import { cli, helpTextLogger } from '@csstools/base-cli'; - -cli( - plugin, - ['preserve'], - helpTextLogger( - 'css-has-pseudo', - 'PostCSS Has Pseudo', - 'Transforms CSS with :has {}', - { - preserve: true, - }, - ), -); diff --git a/plugins/css-has-pseudo/src/encode/decode.mjs b/plugins/css-has-pseudo/src/encode/decode.mjs new file mode 100644 index 000000000..a24302530 --- /dev/null +++ b/plugins/css-has-pseudo/src/encode/decode.mjs @@ -0,0 +1,17 @@ + +/** Decodes an identifier back into a CSS selector */ +export default function decodeCSS(value) { + if (value.slice(0, 13) !== 'csstools-has-') { + return ''; + } + + value = value.slice(13); + let values = value.split('-'); + + let result = ''; + for (let i = 0; i < values.length; i++) { + result += String.fromCharCode(parseInt(values[i], 36)); + } + + return result; +} diff --git a/plugins/css-has-pseudo/src/encode/encode.mjs b/plugins/css-has-pseudo/src/encode/encode.mjs new file mode 100644 index 000000000..36d0a4ae8 --- /dev/null +++ b/plugins/css-has-pseudo/src/encode/encode.mjs @@ -0,0 +1,20 @@ + +/** Returns the string as an encoded CSS identifier. */ +export default function encodeCSS(value) { + if (value === '') { + return ''; + } + + let hex; + let result = ''; + for (let i = 0; i < value.length; i++) { + hex = value.charCodeAt(i).toString(36); + if (i === 0) { + result += hex; + } else { + result += '-' + hex; + } + } + + return 'csstools-has-' + result; +} diff --git a/plugins/css-has-pseudo/src/encode/extract.mjs b/plugins/css-has-pseudo/src/encode/extract.mjs new file mode 100644 index 000000000..366ab150c --- /dev/null +++ b/plugins/css-has-pseudo/src/encode/extract.mjs @@ -0,0 +1,91 @@ +import decodeCSS from './decode.mjs'; + +/** Extract encoded selectors out of attribute selectors */ +export default function extractEncodedSelectors(value) { + let out = []; + + let depth = 0; + let candidate; + + let quoted = false; + let quotedMark; + + let containsUnescapedUnquotedHasAtDepth1 = false; + + // Stryker disable next-line EqualityOperator + for (let i = 0; i < value.length; i++) { + const char = value[i]; + + switch (char) { + case '[': + if (quoted) { + candidate += char; + continue; + } + + if (depth === 0) { + candidate = ''; + } else { + candidate += char; + } + + depth++; + continue; + case ']': + if (quoted) { + candidate += char; + continue; + } + + { + depth--; + if (depth === 0) { + const decoded = decodeCSS(candidate); + if (containsUnescapedUnquotedHasAtDepth1) { + out.push(decoded); + } + } else { + candidate += char; + } + } + + continue; + case '\\': + candidate += value[i]; + candidate += value[i+1]; + i++; + continue; + + case '"': + case '\'': + if (quoted && char === quotedMark) { + quoted = false; + continue; + } else if (quoted) { + candidate += char; + continue; + } + + quoted = true; + quotedMark = char; + continue; + + default: + if (candidate === '' && depth === 1 && (value.slice(i, i + 13) === 'csstools-has-')) { + containsUnescapedUnquotedHasAtDepth1 = true; + } + + candidate += char; + continue; + } + } + + const unique = []; + for (let i = 0; i < out.length; i++) { + if (unique.indexOf(out[i]) === -1) { + unique.push(out[i]); + } + } + + return unique; +} diff --git a/plugins/css-has-pseudo/src/encode/test.mjs b/plugins/css-has-pseudo/src/encode/test.mjs new file mode 100644 index 000000000..7ff593799 --- /dev/null +++ b/plugins/css-has-pseudo/src/encode/test.mjs @@ -0,0 +1,155 @@ +import { strict as assert } from 'assert'; +import encodeCSS from './encode.mjs'; +import decodeCSS from './decode.mjs'; +import extractEncodedSelectors from './extract.mjs'; + +function testEncoderDecoder(decoded, encoded) { + assert.strictEqual(encodeCSS(decoded), encoded); + assert.strictEqual(decodeCSS(encoded), decoded); + + assert.strictEqual(decodeCSS(encodeCSS(decoded)), decoded); + assert.strictEqual(encodeCSS(decodeCSS(encoded)), encoded); + + assert.strictEqual(decodeCSS(encodeCSS(decodeCSS(encodeCSS(decoded)))), decoded); + + assert.strictEqual(decodeCSS(decodeCSS(encodeCSS(encodeCSS(decoded)))), decoded); +} + +testEncoderDecoder( + '', + '', +); + +testEncoderDecoder( + ':has()', + 'csstools-has-1m-2w-2p-37-14-15', +); + +testEncoderDecoder( + ':has( )', + 'csstools-has-1m-2w-2p-37-14-w-15', +); + +testEncoderDecoder( + ':has(*)', + 'csstools-has-1m-2w-2p-37-14-16-15', +); + +testEncoderDecoder( + ':has(:focus)', + 'csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15', +); + +testEncoderDecoder( + ':has(~ p)', + 'csstools-has-1m-2w-2p-37-14-3i-w-34-15', +); + +testEncoderDecoder( + ':has(> p)', + 'csstools-has-1m-2w-2p-37-14-1q-w-34-15', +); + +testEncoderDecoder( + ':has(+ p)', + 'csstools-has-1m-2w-2p-37-14-17-w-34-15', +); + +testEncoderDecoder( + ':has(\\~ p)', + 'csstools-has-1m-2w-2p-37-14-2k-3i-w-34-15', +); + +testEncoderDecoder( + ':has(\\> p)', + 'csstools-has-1m-2w-2p-37-14-2k-1q-w-34-15', +); + +testEncoderDecoder( + ':has(\\+ p)', + 'csstools-has-1m-2w-2p-37-14-2k-17-w-34-15', +); + +testEncoderDecoder( + ':has(\\,\\(\\)\\[\\]\\:\\. p)\\', + 'csstools-has-1m-2w-2p-37-14-2k-18-2k-14-2k-15-2k-2j-2k-2l-2k-1m-2k-1a-w-34-15-2k', +); + +testEncoderDecoder( + ':has(.esc\\\\\\:aped)', + 'csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15', +); + +testEncoderDecoder( + ':has(> [a=":has(.x)"]:hover)', + 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15', +); + +testEncoderDecoder( + ':has(h1, h2, h3, h4, h5, h6)', + 'csstools-has-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15', +); + +testEncoderDecoder( + ':has(> [\\:has\\(\\%3E\\%20\\.a\\:hover\\)', + 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15', +); + +testEncoderDecoder( + ':has(\\%perc)', + 'csstools-has-1m-2w-2p-37-14-2k-11-34-2t-36-2r-15', +); + +testEncoderDecoder( + 'foo', + 'csstools-has-2u-33-33', +); + +testEncoderDecoder( + ':has(> [foo="some"])', + 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15', +); + +testEncoderDecoder( + '#d_main:has(#d_checkbox:checked)>#d_subject', + 'csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2r-2w-2t-2r-2z-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38', +); + +testEncoderDecoder( + '#something-complex:has(> #d_checkbox:checked [foo="some"] * + [bar^="[baz]"] ~ a[class~="logo"] :has(~ .foo:is(button, input)))', + 'csstools-has-z-37-33-31-2t-38-2w-2x-32-2v-19-2r-33-31-34-30-2t-3c-1m-2w-2p-37-14-1q-w-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2r-2w-2t-2r-2z-2t-2s-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-w-16-w-17-w-2j-2q-2p-36-2m-1p-y-2j-2q-2p-3e-2l-y-2l-w-3i-w-2p-2j-2r-30-2p-37-37-3i-1p-y-30-33-2v-33-y-2l-w-1m-2w-2p-37-14-3i-w-1a-2u-33-33-1m-2x-37-14-2q-39-38-38-33-32-18-w-2x-32-34-39-38-15-15-15', +); + +function testExtract(encoded, rules) { + assert.deepStrictEqual(extractEncodedSelectors(encoded), rules); +} + +testExtract( + '', + [], +); + +testExtract( + '.a, [csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15]', + [':has(> [foo="some"])'], +); + +testExtract( + '[csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15], a', + [':has(> [foo="some"])'], +); + +testExtract( + '[csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15][csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15][csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15]', + [':has(> [foo="some"])'], +); + +testExtract( + '[' + encodeCSS('.x:has(> .🧑🏾‍🎤)') +']', + ['.x:has(> .🧑🏾‍🎤)'], +); + +testExtract( + '[' + encodeCSS('.x:has(> .a:has( + .b))') + ']', + ['.x:has(> .a:has( + .b))'], +); diff --git a/plugins/css-has-pseudo/src/index.js b/plugins/css-has-pseudo/src/index.js deleted file mode 100644 index fd433fde5..000000000 --- a/plugins/css-has-pseudo/src/index.js +++ /dev/null @@ -1,156 +0,0 @@ -import parser from 'postcss-selector-parser'; - -const creator = (/** @type {{ preserve: true | false }} */ opts) => { - opts = typeof opts === 'object' && opts || defaultOptions; - - /** Whether the original rule should be preserved. */ - const shouldPreserve = Boolean('preserve' in opts ? opts.preserve : true); - - return { - postcssPlugin: 'css-has-pseudo', - Rule: (rule, { result }) => { - if (!rule.selector.includes(':has(')) { - return; - } - - let modifiedSelector; - - try { - const modifiedSelectorAST = parser((selectors) => { - selectors.walkPseudos(selector => { - if (selector.value === ':has' && selector.nodes) { - const isNotHas = isParentInNotPseudo(selector); - - selector.value = isNotHas ? ':not-has' : ':has'; - - const attribute = parser.attribute({ - attribute: getEscapedCss(String(selector)), - }); - - if (isNotHas) { - selector.parent.parent.replaceWith(attribute); - } else { - selector.replaceWith(attribute); - } - } - }); - }).processSync(rule.selector); - - modifiedSelector = String(modifiedSelectorAST); - } catch (_) { - rule.warn(result, `Failed to parse selector : ${rule.selector}`); - return; - } - - if (typeof modifiedSelector === 'undefined') { - return; - } - - if (modifiedSelector === rule.selector) { - return; - } - - if (shouldPreserve) { - rule.cloneBefore({ selector: modifiedSelector }); - } else { - rule.selector = modifiedSelector; - } - }, - }; -}; - -creator.postcss = true; - -/** Default options. */ -const defaultOptions = { preserve: true }; - -/** Returns the string as an escaped CSS identifier. */ -const getEscapedCss = (/** @type {string} */ value) => { - let out = ''; - let current = ''; - - const flushCurrent = () => { - if (current) { - const encoded = encodeURIComponent(current); - let encodedCurrent = ''; - let encodedOut = ''; - - const flushEncoded = () => { - if (encodedCurrent) { - encodedOut += encodedCurrent; - encodedCurrent = ''; - } - }; - - let encodedEscaped = false; - for (let i = 0; i < encoded.length; i++) { - const char = encoded[i]; - - if (encodedEscaped) { - encodedCurrent += char; - encodedEscaped = false; - continue; - } - - switch (char) { - case '%': - flushEncoded(); - encodedOut += ( '\\' + char ); - continue; - case '\\': - encodedCurrent += char; - encodedEscaped = true; - continue; - - default: - encodedCurrent += char; - continue; - } - } - - flushEncoded(); - out += encodedOut; - current = ''; - } - }; - - let escaped = false; - for (let i = 0; i < value.length; i++) { - const char = value[i]; - - if (escaped) { - current += char; - escaped = false; - continue; - } - - switch (char) { - case ':': - case '[': - case ']': - case ',': - case '(': - case ')': - flushCurrent(); - out += ( '\\' + char ); - continue; - case '\\': - current += char; - escaped = true; - continue; - - default: - current += char; - continue; - } - } - - flushCurrent(); - - return out; -}; - -/** Returns whether the selector is within a `:not` pseudo-class. */ -const isParentInNotPseudo = (selector) => selector.parent?.parent?.type === 'pseudo' && selector.parent.parent.value === ':not'; - -export default creator; diff --git a/plugins/css-has-pseudo/src/index.ts b/plugins/css-has-pseudo/src/index.ts new file mode 100644 index 000000000..cabc31012 --- /dev/null +++ b/plugins/css-has-pseudo/src/index.ts @@ -0,0 +1,120 @@ +import parser from 'postcss-selector-parser'; +import { selectorSpecificity } from '@csstools/selector-specificity'; +import encodeCSS from './encode/encode.mjs'; +import type { PluginCreator } from 'postcss'; + +const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: string }> = (opts?: { preserve?: boolean, specificityMatchingName?: string }) => { + const options = { + preserve: true, + specificityMatchingName: 'does-not-exist', + ...(opts || {}), + }; + + const specificityMatchingNameId = ':not(#' + options.specificityMatchingName + ')'; + const specificityMatchingNameClass = ':not(.' + options.specificityMatchingName + ')'; + const specificityMatchingNameTag = ':not(' + options.specificityMatchingName + ')'; + + return { + postcssPlugin: 'css-has-pseudo-experimental', + RuleExit: (rule, { result }) => { + if (!rule.selector.includes(':has(') || isWithinSupportCheck(rule)) { + return; + } + + const selectors = rule.selectors.map((selector) => { + if (!selector.includes(':has(')) { + return selector; + } + + let selectorAST; + try { + selectorAST = parser().astSync(selector); + } catch (_) { + rule.warn(result, `Failed to parse selector : ${selector}`); + return selector; + } + + if (typeof selectorAST === 'undefined') { + return selector; + } + + let containsHasPseudo = false; + selectorAST.walkPseudos((node) => { + containsHasPseudo = containsHasPseudo || (node.value === ':has' && node.nodes); + + // see : https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c34 + // When we have ':has(:visited) {...}', the subject elements of the rule + // are the ancestors of the visited link element. + + // To prevent leaking visitedness to the link's ancestors, the ':visited' + // selector does not match if it is inside the ':has()' argument selector. + // So if a ':has()' argument selector requires a matching ':visited', the + // style rule are not applied. + if (node.value === ':visited') { + // We can't leave `:has` untouched as that might cause broken selector lists. + // Replacing with the specificity matching name as this should never match anything without `:not()`. + node.replaceWith(parser.className({ + value: options.specificityMatchingName, + })); + } + + if (node.value === ':any-link') { + // we can transform `:any-link` to `:link` as this is allowed + node.value = ':link'; + } + }); + + if (!containsHasPseudo) { + return selector; + } + + const encodedSelector = '[' + encodeCSS(selectorAST.toString()) + ']'; + const abcSpecificity = selectorSpecificity(selectorAST); + + let encodedSelectorWithSpecificity = encodedSelector; + for (let i = 0; i < abcSpecificity.a; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameId; + } + const bSpecificity = Math.max(1, abcSpecificity.b) - 1; + for (let i = 0; i < bSpecificity; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameClass; + } + for (let i = 0; i < abcSpecificity.c; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameTag; + } + + return encodedSelectorWithSpecificity; + }); + + if (selectors.join(',') === rule.selectors.join(',')) { + return; + } + + if (options.preserve) { + rule.cloneBefore({ selectors: selectors }); + } else { + rule.selectors = selectors; + } + }, + }; +}; + +creator.postcss = true; + +export default creator; + +function isWithinSupportCheck(rule) { + let isSupportCheck = false; + let ruleParent = rule.parent; + + while (!isSupportCheck && ruleParent) { + if (ruleParent.type === 'atrule') { + + isSupportCheck = ruleParent.params.includes(':has(') && ruleParent.params.startsWith('selector('); + } + + ruleParent = ruleParent.parent; + } + + return isSupportCheck; +} diff --git a/plugins/css-has-pseudo/test/_browser.html b/plugins/css-has-pseudo/test/_browser.html new file mode 100644 index 000000000..4d8c007f9 --- /dev/null +++ b/plugins/css-has-pseudo/test/_browser.html @@ -0,0 +1,1189 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/css-has-pseudo/test/_browser.mjs b/plugins/css-has-pseudo/test/_browser.mjs new file mode 100644 index 000000000..a8220d97c --- /dev/null +++ b/plugins/css-has-pseudo/test/_browser.mjs @@ -0,0 +1,83 @@ +import puppeteer from 'puppeteer'; +import http from 'http'; +import { promises as fsp } from 'fs'; + +(async () => { + const requestListener = async function (req, res) { + + const parsedUrl = new URL(req.url, 'http://localhost:8080'); + const pathname = parsedUrl.pathname; + + switch (pathname) { + case '': + case '/': + res.setHeader('Content-type', 'text/html'); + res.writeHead(200); + res.end(await fsp.readFile('test/_browser.html', 'utf8')); + break; + case '/test/basic.expect.css': + // Stylesheet WITHOUT CORS headers + res.setHeader('Content-type', 'text/css'); + res.writeHead(200); + res.end(await fsp.readFile('test/basic.expect.css', 'utf8')); + break; + case '/test/browser.expect.css': + // Stylesheet WITH CORS headers + res.setHeader('Content-type', 'text/css'); + res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); + res.writeHead(200); + res.end(await fsp.readFile('test/browser.expect.css', 'utf8')); + break; + case '/dist/browser-global.js': + res.setHeader('Content-type', 'text/javascript'); + res.writeHead(200); + res.end(await fsp.readFile('dist/browser-global.js', 'utf8')); + break; + + default: + res.setHeader('Content-type', 'text/plain' ); + res.writeHead(404); + res.end('Not found'); + break; + } + }; + + // Use different servers for HTML/CSS/JS to trigger CORS + // + // HTML: + const serverA = http.createServer(requestListener); + serverA.listen(8080); + // CSS: + const serverB = http.createServer(requestListener); + serverB.listen(8081); + // JS: + const serverC = http.createServer(requestListener); + serverC.listen(8082); + + if (!process.env.DEBUG) { + const browser = await puppeteer.launch({ + headless: true, + }); + + const page = await browser.newPage(); + page.on('pageerror', (msg) => { + throw msg; + }); + await page.goto('http://localhost:8080'); + const result = await page.evaluate(async() => { + // eslint-disable-next-line no-undef + return await window.runTest(); + }); + if (!result) { + throw new Error('Test failed, expected "window.runTest()" to return true'); + } + + await browser.close(); + + await serverA.close(); + await serverB.close(); + await serverC.close(); + } else { + console.log('visit : http://localhost:8080'); + } +})(); diff --git a/plugins/css-has-pseudo/test/basic.css b/plugins/css-has-pseudo/test/basic.css index cc1a4b0d6..e37868806 100644 --- a/plugins/css-has-pseudo/test/basic.css +++ b/plugins/css-has-pseudo/test/basic.css @@ -10,23 +10,135 @@ h1:has(+ p) { order: 3; } -section:not(:has(h1, h2, h3, h4, h5, h6)) { +h1:has(~ p) { order: 4; } +section:not(:has(h1, h2, h3, h4, h5, h6)) { + order: 5; +} + body:has(:focus) { - order: 5; + order: 6; } body:not(:has(:focus)) { - order: 5; + order: 7; } :has-ignore(:focus), :ignore-has(:focus) { - order: 6; + order: 8; } -:has(.esc\:aped) { - order: 7; +:has(.esc\\\:aped) { + order: 9; +} + +.x:has(> .a:hover) { + order: 10; +} + +.x:has(> #a:hover) { + order: 11; +} + +.x:has(> [a]:hover) { + order: 12; +} + +.x:has(> [a="b"]:hover) { + order: 13; +} + +.x:has(> [a=":has(.x)"]:hover) { + order: 14; +} + +.x:has(> [\:has\(\%3E\%20\.a\:hover\)]:hover) { + order: 15; +} + +.x:has(> ::before:hover) { + order: 16; /* not allowed by spec but encoding should work */ +} + +.x:has(> .a:has( + .b)) { + order: 17; +} + +.x:has(> __foo) { + order: 18; +} + +.x:has(> :--foo) { + order: 19; +} + +.x:has(> *) { + order: 20; +} + +.x:has(> .y *) { + order: 21; +} + +.a:not(:has(> .b)) { + order: 22; +} + +.x:has(~ .y:has(.g .h) .i) { + order: 23; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 24; +} + +.x:has(> .a) .b { + order: 24; +} + +.x:has(> .🧑🏾‍🎤) { + order: 25; +} + +.x:has(> .a), .x:has(> .b) { + order: 26; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 27; +} + +.x:has(> .a), .b { + order: 28; +} + +.a, .x:has(> .b) { + order: 29; +} + +.x:has(> .b *) { + order: 30; +} + +.x:has(> :visited) { + order: 31; +} + +.x:has(> :any-link) { + order: 31; +} + +@supports selector(:has(:focus)) { + :has(:focus) { + order: 32; + } +} + +@supports (display: grid) { + :has(:focus) { + order: 33; + } } diff --git a/plugins/css-has-pseudo/test/basic.expect.css b/plugins/css-has-pseudo/test/basic.expect.css index efcd66452..052c39c82 100644 --- a/plugins/css-has-pseudo/test/basic.expect.css +++ b/plugins/css-has-pseudo/test/basic.expect.css @@ -1,4 +1,4 @@ -[\:has\(\:focus\)] { +[csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { order: 1; } @@ -6,7 +6,7 @@ order: 1; } -a[\:has\(\%3E\%20img\)] { +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) { order: 2; } @@ -14,7 +14,7 @@ a:has(> img) { order: 2; } -h1[\:has\(\%2B\%20p\)] { +[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { order: 3; } @@ -22,39 +22,254 @@ h1:has(+ p) { order: 3; } -section[\:not-has\(h1\,\%20h2\,\%20h3\,\%20h4\,\%20h5\,\%20h6\)] { +[csstools-has-2w-1d-1m-2w-2p-37-14-3i-w-34-15]:not(does-not-exist):not(does-not-exist) { order: 4; } -section:not(:has(h1, h2, h3, h4, h5, h6)) { +h1:has(~ p) { order: 4; } -body[\:has\(\:focus\)] { - order: 5; +[csstools-has-37-2t-2r-38-2x-33-32-1m-32-33-38-14-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15-15]:not(does-not-exist):not(does-not-exist) { + order: 5; +} + +section:not(:has(h1, h2, h3, h4, h5, h6)) { + order: 5; +} + +[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { + order: 6; } body:has(:focus) { - order: 5; + order: 6; } -body[\:not-has\(\:focus\)] { - order: 5; +[csstools-has-2q-33-2s-3d-1m-32-33-38-14-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15-15]:not(does-not-exist) { + order: 7; } body:not(:has(:focus)) { - order: 5; + order: 7; } :has-ignore(:focus), :ignore-has(:focus) { - order: 6; + order: 8; } -[\:has\(.esc\%5C\%3Aaped\)] { - order: 7; +[csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15] { + order: 9; } -:has(.esc\:aped) { - order: 7; +:has(.esc\\\:aped) { + order: 9; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 10; +} + +.x:has(> .a:hover) { + order: 10; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { + order: 11; +} + +.x:has(> #a:hover) { + order: 11; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 12; +} + +.x:has(> [a]:hover) { + order: 12; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-2q-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 13; +} + +.x:has(> [a="b"]:hover) { + order: 13; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 14; +} + +.x:has(> [a=":has(.x)"]:hover) { + order: 14; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 15; +} + +.x:has(> [\:has\(\%3E\%20\.a\:hover\)]:hover) { + order: 15; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-1m-2q-2t-2u-33-36-2t-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(does-not-exist) { + order: 16; /* not allowed by spec but encoding should work */ +} + +.x:has(> ::before:hover) { + order: 16; /* not allowed by spec but encoding should work */ +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-2p-37-14-w-17-w-1a-2q-15-15]:not(.does-not-exist):not(.does-not-exist) { + order: 17; +} + +.x:has(> .a:has( + .b)) { + order: 17; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2n-2n-2u-33-33-15]:not(does-not-exist) { + order: 18; +} + +.x:has(> __foo) { + order: 18; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-19-19-2u-33-33-15]:not(.does-not-exist) { + order: 19; +} + +.x:has(> :--foo) { + order: 19; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-16-15] { + order: 20; +} + +.x:has(> *) { + order: 20; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-3d-w-16-15]:not(.does-not-exist) { + order: 21; +} + +.x:has(> .y *) { + order: 21; +} + +[csstools-has-1a-2p-1m-32-33-38-14-1m-2w-2p-37-14-1q-w-1a-2q-15-15]:not(.does-not-exist) { + order: 22; +} + +.a:not(:has(> .b)) { + order: 22; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-3i-w-1a-3d-1m-2w-2p-37-14-1a-2v-w-1a-2w-15-w-1a-2x-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 23; +} + +.x:has(~ .y:has(.g .h) .i) { + order: 23; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 24; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-1a-2q]:not(.does-not-exist):not(.does-not-exist) { + order: 24; +} + +.x:has(> .a) .b { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-16pq-17td-16po-188u-6bx-16po-186c-15]:not(.does-not-exist) { + order: 25; +} + +.x:has(> .🧑🏾‍🎤) { + order: 25; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { + order: 26; +} + +.x:has(> .a), .x:has(> .b) { + order: 26; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 27; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 27; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), .b { + order: 28; +} + +.x:has(> .a), .b { + order: 28; +} + +.a, [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { + order: 29; +} + +.a, .x:has(> .b) { + order: 29; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-w-16-15]:not(.does-not-exist) { + order: 30; +} + +.x:has(> .b *) { + order: 30; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(.does-not-exist) { + order: 31; +} + +.x:has(> :visited) { + order: 31; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-30-2x-32-2z-15]:not(.does-not-exist) { + order: 31; +} + +.x:has(> :any-link) { + order: 31; +} + +@supports selector(:has(:focus)) { + :has(:focus) { + order: 32; + } +} + +@supports (display: grid) { + [csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { + order: 33; + } + :has(:focus) { + order: 33; + } } diff --git a/plugins/css-has-pseudo/test/basic.preserve.expect.css b/plugins/css-has-pseudo/test/basic.preserve.expect.css index 9cf08f542..053bc256d 100644 --- a/plugins/css-has-pseudo/test/basic.preserve.expect.css +++ b/plugins/css-has-pseudo/test/basic.preserve.expect.css @@ -1,32 +1,144 @@ -[\:has\(\:focus\)] { +[csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { order: 1; } -a[\:has\(\%3E\%20img\)] { +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) { order: 2; } -h1[\:has\(\%2B\%20p\)] { +[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { order: 3; } -section[\:not-has\(h1\,\%20h2\,\%20h3\,\%20h4\,\%20h5\,\%20h6\)] { +[csstools-has-2w-1d-1m-2w-2p-37-14-3i-w-34-15]:not(does-not-exist):not(does-not-exist) { order: 4; } -body[\:has\(\:focus\)] { - order: 5; +[csstools-has-37-2t-2r-38-2x-33-32-1m-32-33-38-14-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15-15]:not(does-not-exist):not(does-not-exist) { + order: 5; } -body[\:not-has\(\:focus\)] { - order: 5; +[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { + order: 6; +} + +[csstools-has-2q-33-2s-3d-1m-32-33-38-14-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15-15]:not(does-not-exist) { + order: 7; } :has-ignore(:focus), :ignore-has(:focus) { - order: 6; + order: 8; } -[\:has\(.esc\%5C\%3Aaped\)] { - order: 7; +[csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15] { + order: 9; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 10; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { + order: 11; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 12; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-2q-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 13; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 14; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { + order: 15; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-1m-2q-2t-2u-33-36-2t-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(does-not-exist) { + order: 16; /* not allowed by spec but encoding should work */ +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-2p-37-14-w-17-w-1a-2q-15-15]:not(.does-not-exist):not(.does-not-exist) { + order: 17; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2n-2n-2u-33-33-15]:not(does-not-exist) { + order: 18; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-19-19-2u-33-33-15]:not(.does-not-exist) { + order: 19; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-16-15] { + order: 20; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-3d-w-16-15]:not(.does-not-exist) { + order: 21; +} + +[csstools-has-1a-2p-1m-32-33-38-14-1m-2w-2p-37-14-1q-w-1a-2q-15-15]:not(.does-not-exist) { + order: 22; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-3i-w-1a-3d-1m-2w-2p-37-14-1a-2v-w-1a-2w-15-w-1a-2x-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 23; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-1a-2q]:not(.does-not-exist):not(.does-not-exist) { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-16pq-17td-16po-188u-6bx-16po-186c-15]:not(.does-not-exist) { + order: 25; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { + order: 26; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { + order: 27; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), .b { + order: 28; +} + +.a, [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { + order: 29; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-w-16-15]:not(.does-not-exist) { + order: 30; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(.does-not-exist) { + order: 31; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-30-2x-32-2z-15]:not(.does-not-exist) { + order: 31; +} + +@supports selector(:has(:focus)) { + :has(:focus) { + order: 32; + } +} + +@supports (display: grid) { + [csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { + order: 33; + } } diff --git a/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css b/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css new file mode 100644 index 000000000..cbc4287ec --- /dev/null +++ b/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css @@ -0,0 +1,275 @@ +[csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { + order: 1; +} + +:has(:focus) { + order: 1; +} + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(other-thing-that-does-not-exist):not(other-thing-that-does-not-exist) { + order: 2; +} + +a:has(> img) { + order: 2; +} + +[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(other-thing-that-does-not-exist):not(other-thing-that-does-not-exist) { + order: 3; +} + +h1:has(+ p) { + order: 3; +} + +[csstools-has-2w-1d-1m-2w-2p-37-14-3i-w-34-15]:not(other-thing-that-does-not-exist):not(other-thing-that-does-not-exist) { + order: 4; +} + +h1:has(~ p) { + order: 4; +} + +[csstools-has-37-2t-2r-38-2x-33-32-1m-32-33-38-14-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15-15]:not(other-thing-that-does-not-exist):not(other-thing-that-does-not-exist) { + order: 5; +} + +section:not(:has(h1, h2, h3, h4, h5, h6)) { + order: 5; +} + +[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(other-thing-that-does-not-exist) { + order: 6; +} + +body:has(:focus) { + order: 6; +} + +[csstools-has-2q-33-2s-3d-1m-32-33-38-14-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15-15]:not(other-thing-that-does-not-exist) { + order: 7; +} + +body:not(:has(:focus)) { + order: 7; +} + +:has-ignore(:focus), +:ignore-has(:focus) { + order: 8; +} + +[csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15] { + order: 9; +} + +:has(.esc\\\:aped) { + order: 9; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 10; +} + +.x:has(> .a:hover) { + order: 10; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 11; +} + +.x:has(> #a:hover) { + order: 11; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-2l-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 12; +} + +.x:has(> [a]:hover) { + order: 12; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-2q-y-2l-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 13; +} + +.x:has(> [a="b"]:hover) { + order: 13; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 14; +} + +.x:has(> [a=":has(.x)"]:hover) { + order: 14; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15-2l-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 15; +} + +.x:has(> [\:has\(\%3E\%20\.a\:hover\)]:hover) { + order: 15; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-1m-2q-2t-2u-33-36-2t-1m-2w-33-3a-2t-36-15]:not(.other-thing-that-does-not-exist):not(other-thing-that-does-not-exist) { + order: 16; /* not allowed by spec but encoding should work */ +} + +.x:has(> ::before:hover) { + order: 16; /* not allowed by spec but encoding should work */ +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-2p-37-14-w-17-w-1a-2q-15-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 17; +} + +.x:has(> .a:has( + .b)) { + order: 17; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2n-2n-2u-33-33-15]:not(other-thing-that-does-not-exist) { + order: 18; +} + +.x:has(> __foo) { + order: 18; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-19-19-2u-33-33-15]:not(.other-thing-that-does-not-exist) { + order: 19; +} + +.x:has(> :--foo) { + order: 19; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-16-15] { + order: 20; +} + +.x:has(> *) { + order: 20; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-3d-w-16-15]:not(.other-thing-that-does-not-exist) { + order: 21; +} + +.x:has(> .y *) { + order: 21; +} + +[csstools-has-1a-2p-1m-32-33-38-14-1m-2w-2p-37-14-1q-w-1a-2q-15-15]:not(.other-thing-that-does-not-exist) { + order: 22; +} + +.a:not(:has(> .b)) { + order: 22; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-3i-w-1a-3d-1m-2w-2p-37-14-1a-2v-w-1a-2w-15-w-1a-2x-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 23; +} + +.x:has(~ .y:has(.g .h) .i) { + order: 23; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 24; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-1a-2q]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 24; +} + +.x:has(> .a) .b { + order: 24; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-16pq-17td-16po-188u-6bx-16po-186c-15]:not(.other-thing-that-does-not-exist) { + order: 25; +} + +.x:has(> .🧑🏾‍🎤) { + order: 25; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.other-thing-that-does-not-exist), [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.other-thing-that-does-not-exist) { + order: 26; +} + +.x:has(> .a), .x:has(> .b) { + order: 26; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist):not(.other-thing-that-does-not-exist) { + order: 27; +} + +.x:has(> .a) ~ .x:has(> .b) { + order: 27; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.other-thing-that-does-not-exist), .b { + order: 28; +} + +.x:has(> .a), .b { + order: 28; +} + +.a, [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.other-thing-that-does-not-exist) { + order: 29; +} + +.a, .x:has(> .b) { + order: 29; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-w-16-15]:not(.other-thing-that-does-not-exist) { + order: 30; +} + +.x:has(> .b *) { + order: 30; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-33-38-2w-2t-36-19-38-2w-2x-32-2v-19-38-2w-2p-38-19-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(.other-thing-that-does-not-exist) { + order: 31; +} + +.x:has(> :visited) { + order: 31; +} + +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-30-2x-32-2z-15]:not(.other-thing-that-does-not-exist) { + order: 31; +} + +.x:has(> :any-link) { + order: 31; +} + +@supports selector(:has(:focus)) { + :has(:focus) { + order: 32; + } +} + +@supports (display: grid) { + [csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { + order: 33; + } + :has(:focus) { + order: 33; + } +} diff --git a/plugins/css-has-pseudo/test/browser.css b/plugins/css-has-pseudo/test/browser.css new file mode 100644 index 000000000..186b159e9 --- /dev/null +++ b/plugins/css-has-pseudo/test/browser.css @@ -0,0 +1,184 @@ +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-pseudo-class.html */ +#d_main:has(input) div { + color: grey +} + +#d_main:has(#d_checkbox:checked)>#d_subject { + color: red +} + +#d_main:has(#d_option:checked)>#d_subject { + color: red +} + +#d_main:has(#d_checkbox:disabled)>#d_subject { + color: green +} + +#d_main:has(#d_option:disabled)> :is(#d_subject, #d_subject2) { + color: green +} + +#d_main:has(#d_optgroup:disabled)>#d_subject { + color: blue +} + +#d_main:not(:has(#d_checkbox:enabled))>#d_subject3 { + color: green +} + +#d_main:not(:has(#d_option:enabled)) :is(#d_subject3, #d_subject4) { + color: green +} + +#d_main:not(:has(#d_optgroup:enabled))>#d_subject3 { + color: blue +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-in-ancestor-position.html */ +div:has(.c_test, [c_test_attr]) #c_subject { + background-color: red +} + +div:has(> .c_test, > [c_test_attr]) #c_subject { + background-color: green +} + +div:has(~ .c_test, ~ [c_test_attr]) #c_subject { + background-color: yellow +} + +div:has(+ .c_test, + [c_test_attr]) #c_subject { + background-color: blue +} + +div:has(~ div .c_test, ~ div [c_test_attr]) #c_subject { + background-color: purple +} + +div:has(+ div .c_test, + div [c_test_attr]) #c_subject { + background-color: pink +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-in-adjacent-position.html */ +div:has(.a_Test, [a_test_attr])+#a_subject { + background-color: red; +} + +div:has(> .a_Test, > [a_test_attr])+#a_subject { + background-color: green; +} + +div:has(~ .a_Test, ~ [a_test_attr])+#a_subject { + background-color: yellow; +} + +div:has(+ .a_Test, + [a_test_attr])+#a_subject { + background-color: blue; +} + +div:has(~ div .a_Test, ~ div [a_test_attr])+#a_subject { + background-color: purple; +} + +div:has(+ div .a_Test, + div [a_test_attr])+#a_subject { + background-color: pink; +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/attribute-or-elemental-selectors-in-has.html */ +div, +main { + background-color: grey; + margin: 5px; + min-height: 20px; + min-width: 20px; + padding: 5px; +} + +.b_subject:has(> .b_child) { + background-color: red; +} + +.b_subject:has(.b_descendant) { + background-color: green; +} + +.b_subject:has([attrname="b_descendant"]) { + background-color: blue; +} + +.b_subject:has(#b_div_descendant) { + background-color: yellow; +} + +.b_subject:has(b_descendant) { + background-color: yellowgreen; +} + +#visited-1:has(:link) { + color: green; +} + +#visited-2:has(:visited) { + color: yellow; +} + +#visited-3:has(:any-link) { + color: yellowgreen; +} + +#visited-4:has(:visited) { + color: red; +} + +#main_specificity :has(#foo) { + --t0:PASS; +} + +#main_specificity :has(.foo) { + --t0:FAIL; +} + +#main_specificity :has(span#foo) { + --t1:PASS; +} + +#main_specificity :has(#foo) { + --t1:FAIL; +} + +#main_specificity :has(.bar, #foo) { + --t2:FAIL; +} + +#main_specificity :has(#foo, .bar) { + --t2:PASS; +} + +#main_specificity :has(.bar, #foo) { + --t3:PASS; +} + +#main_specificity :has(.foo, .bar) { + --t3:FAIL; +} + +#main_specificity :has(span + span) { + --t4:PASS; +} + +#main_specificity :has(span) { + --t4:FAIL; +} + +#main_specificity :has(span, li, #foo) { + --t5:PASS; +} + +#main_specificity :has(span, li, p) { + --t5:FAIL; +} + +#nested_main :has(> :has(#c)) { + color: red; +} diff --git a/plugins/css-has-pseudo/test/browser.expect.css b/plugins/css-has-pseudo/test/browser.expect.css new file mode 100644 index 000000000..8e1cc40a9 --- /dev/null +++ b/plugins/css-has-pseudo/test/browser.expect.css @@ -0,0 +1,184 @@ +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-pseudo-class.html */ +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-2x-32-34-39-38-15-w-2s-2x-3a]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + color: grey +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2r-2w-2t-2r-2z-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: red +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-33-34-38-2x-33-32-1m-2r-2w-2t-2r-2z-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: red +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2s-2x-37-2p-2q-30-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: green +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-33-34-38-2x-33-32-1m-2s-2x-37-2p-2q-30-2t-2s-15-1q-w-1m-2x-37-14-z-2s-2n-37-39-2q-2y-2t-2r-38-18-w-z-2s-2n-37-39-2q-2y-2t-2r-38-1e-15]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: green +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-33-34-38-2v-36-33-39-34-1m-2s-2x-37-2p-2q-30-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: blue +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-32-33-38-14-1m-2w-2p-37-14-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2t-32-2p-2q-30-2t-2s-15-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38-1f]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: green +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-32-33-38-14-1m-2w-2p-37-14-z-2s-2n-33-34-38-2x-33-32-1m-2t-32-2p-2q-30-2t-2s-15-15-w-1m-2x-37-14-z-2s-2n-37-39-2q-2y-2t-2r-38-1f-18-w-z-2s-2n-37-39-2q-2y-2t-2r-38-1g-15]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: green +} + +[csstools-has-z-2s-2n-31-2p-2x-32-1m-32-33-38-14-1m-2w-2p-37-14-z-2s-2n-33-34-38-2v-36-33-39-34-1m-2t-32-2p-2q-30-2t-2s-15-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38-1f]:not(#does-not-exist):not(#does-not-exist):not(#does-not-exist) { + color: blue +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-in-ancestor-position.html */ +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-1a-2r-2n-38-2t-37-38-18-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: red +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-1q-w-1a-2r-2n-38-2t-37-38-18-w-1q-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: green +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-3i-w-1a-2r-2n-38-2t-37-38-18-w-3i-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: yellow +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-17-w-1a-2r-2n-38-2t-37-38-18-w-17-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: blue +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-3i-w-2s-2x-3a-w-1a-2r-2n-38-2t-37-38-18-w-3i-w-2s-2x-3a-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + background-color: purple +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-17-w-2s-2x-3a-w-1a-2r-2n-38-2t-37-38-18-w-17-w-2s-2x-3a-w-2j-2r-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-w-z-2r-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + background-color: pink +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/has-in-adjacent-position.html */ +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-1a-2p-2n-2c-2t-37-38-18-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: red; +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-1q-w-1a-2p-2n-2c-2t-37-38-18-w-1q-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: green; +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-3i-w-1a-2p-2n-2c-2t-37-38-18-w-3i-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: yellow; +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-17-w-1a-2p-2n-2c-2t-37-38-18-w-17-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist) { + background-color: blue; +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-3i-w-2s-2x-3a-w-1a-2p-2n-2c-2t-37-38-18-w-3i-w-2s-2x-3a-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + background-color: purple; +} + +[csstools-has-2s-2x-3a-1m-2w-2p-37-14-17-w-2s-2x-3a-w-1a-2p-2n-2c-2t-37-38-18-w-17-w-2s-2x-3a-w-2j-2p-2n-38-2t-37-38-2n-2p-38-38-36-2l-15-17-z-2p-2n-37-39-2q-2y-2t-2r-38]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + background-color: pink; +} + +/* https://github.com/web-platform-tests/wpt/blob/master/css/selectors/invalidation/attribute-or-elemental-selectors-in-has.html */ +div, +main { + background-color: grey; + margin: 5px; + min-height: 20px; + min-width: 20px; + padding: 5px; +} + +[csstools-has-1a-2q-2n-37-39-2q-2y-2t-2r-38-1m-2w-2p-37-14-1q-w-1a-2q-2n-2r-2w-2x-30-2s-15]:not(.does-not-exist) { + background-color: red; +} + +[csstools-has-1a-2q-2n-37-39-2q-2y-2t-2r-38-1m-2w-2p-37-14-1a-2q-2n-2s-2t-37-2r-2t-32-2s-2p-32-38-15]:not(.does-not-exist) { + background-color: green; +} + +[csstools-has-1a-2q-2n-37-39-2q-2y-2t-2r-38-1m-2w-2p-37-14-2j-2p-38-38-36-32-2p-31-2t-1p-y-2q-2n-2s-2t-37-2r-2t-32-2s-2p-32-38-y-2l-15]:not(.does-not-exist) { + background-color: blue; +} + +[csstools-has-1a-2q-2n-37-39-2q-2y-2t-2r-38-1m-2w-2p-37-14-z-2q-2n-2s-2x-3a-2n-2s-2t-37-2r-2t-32-2s-2p-32-38-15]:not(#does-not-exist) { + background-color: yellow; +} + +[csstools-has-1a-2q-2n-37-39-2q-2y-2t-2r-38-1m-2w-2p-37-14-2q-2n-2s-2t-37-2r-2t-32-2s-2p-32-38-15]:not(does-not-exist) { + background-color: yellowgreen; +} + +[csstools-has-z-3a-2x-37-2x-38-2t-2s-19-1d-1m-2w-2p-37-14-1m-30-2x-32-2z-15]:not(#does-not-exist) { + color: green; +} + +[csstools-has-z-3a-2x-37-2x-38-2t-2s-19-1e-1m-2w-2p-37-14-1a-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(#does-not-exist) { + color: yellow; +} + +[csstools-has-z-3a-2x-37-2x-38-2t-2s-19-1f-1m-2w-2p-37-14-1m-30-2x-32-2z-15]:not(#does-not-exist) { + color: yellowgreen; +} + +[csstools-has-z-3a-2x-37-2x-38-2t-2s-19-1g-1m-2w-2p-37-14-1a-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(#does-not-exist) { + color: red; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist) { + --t0:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { + --t0:FAIL; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-37-34-2p-32-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist):not(does-not-exist) { + --t1:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist) { + --t1:FAIL; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-1a-2q-2p-36-18-w-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist) { + --t2:FAIL; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-z-2u-33-33-18-w-1a-2q-2p-36-15]:not(#does-not-exist):not(#does-not-exist) { + --t2:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-1a-2q-2p-36-18-w-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist) { + --t3:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-1a-2u-33-33-18-w-1a-2q-2p-36-15]:not(#does-not-exist) { + --t3:FAIL; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-37-34-2p-32-w-17-w-37-34-2p-32-15]:not(#does-not-exist):not(does-not-exist):not(does-not-exist) { + --t4:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-37-34-2p-32-15]:not(#does-not-exist):not(does-not-exist) { + --t4:FAIL; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-37-34-2p-32-18-w-30-2x-18-w-z-2u-33-33-15]:not(#does-not-exist):not(#does-not-exist) { + --t5:PASS; +} + +[csstools-has-z-31-2p-2x-32-2n-37-34-2t-2r-2x-2u-2x-2r-2x-38-3d-w-1m-2w-2p-37-14-37-34-2p-32-18-w-30-2x-18-w-34-15]:not(#does-not-exist):not(does-not-exist) { + --t5:FAIL; +} + +[csstools-has-z-32-2t-37-38-2t-2s-2n-31-2p-2x-32-w-1m-2w-2p-37-14-1q-w-1m-2w-2p-37-14-z-2r-15-15]:not(#does-not-exist):not(#does-not-exist) { + color: red; +} diff --git a/plugins/css-has-pseudo/test/examples/example.css b/plugins/css-has-pseudo/test/examples/example.css new file mode 100644 index 000000000..8ad2ea81e --- /dev/null +++ b/plugins/css-has-pseudo/test/examples/example.css @@ -0,0 +1,3 @@ +.title:has(+ p) { + margin-bottom: 1.5rem; +} diff --git a/plugins/css-has-pseudo/test/examples/example.expect.css b/plugins/css-has-pseudo/test/examples/example.expect.css new file mode 100644 index 000000000..451df21af --- /dev/null +++ b/plugins/css-has-pseudo/test/examples/example.expect.css @@ -0,0 +1,6 @@ +[csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { + margin-bottom: 1.5rem; +} +.title:has(+ p) { + margin-bottom: 1.5rem; +} diff --git a/plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css b/plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css new file mode 100644 index 000000000..451df21af --- /dev/null +++ b/plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css @@ -0,0 +1,6 @@ +[csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { + margin-bottom: 1.5rem; +} +.title:has(+ p) { + margin-bottom: 1.5rem; +} diff --git a/plugins/css-has-pseudo/test/generated-selector-cases.expect.css b/plugins/css-has-pseudo/test/generated-selector-cases.expect.css index 36825a5ce..a40c3a861 100644 --- a/plugins/css-has-pseudo/test/generated-selector-cases.expect.css +++ b/plugins/css-has-pseudo/test/generated-selector-cases.expect.css @@ -1,880 +1,880 @@ -[\:has\(.foo\)][\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 0; } -[\:has\(.foo\)][\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 1; } -[\:has\(.foo\)] [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 2; } -[\:has\(.foo\)] [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 3; } -[\:has\(.foo\)] [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 4; } -[\:has\(.foo\)] [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 5; } -[\:has\(.foo\)]+[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 6; } -[\:has\(.foo\)]+[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 7; } -[\:has\(.foo\)] + [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 8; } -[\:has\(.foo\)] + [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 9; } -[\:has\(.foo\)]~[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 10; } -[\:has\(.foo\)]~[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 11; } -[\:has\(.foo\)] ~ [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 12; } -[\:has\(.foo\)] ~ [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 13; } -[\:has\(.foo\)]>[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 14; } -[\:has\(.foo\)]>[\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 15; } -[\:has\(.foo\)] > [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 16; } -[\:has\(.foo\)] > [\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 17; } -[\:has\(.foo\)],[\%20\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 18; } -[\:has\(.foo\)],[\%20\:has\(.foo\)] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 19; } -button[\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 20; } -[\:has\(.foo\)]button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-2q-39-38-38-33-32]:not(does-not-exist) { order: 21; } -button [\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 22; } -[\:has\(.foo\)] button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-2q-39-38-38-33-32]:not(does-not-exist) { order: 23; } -button [\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 24; } -[\:has\(.foo\)] button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-2q-39-38-38-33-32]:not(does-not-exist) { order: 25; } -button+[\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 26; } -[\:has\(.foo\)]+button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-2q-39-38-38-33-32]:not(does-not-exist) { order: 27; } -button + [\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 28; } -[\:has\(.foo\)] + button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-2q-39-38-38-33-32]:not(does-not-exist) { order: 29; } -button~[\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 30; } -[\:has\(.foo\)]~button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-2q-39-38-38-33-32]:not(does-not-exist) { order: 31; } -button ~ [\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 32; } -[\:has\(.foo\)] ~ button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-2q-39-38-38-33-32]:not(does-not-exist) { order: 33; } -button>[\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 34; } -[\:has\(.foo\)]>button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-2q-39-38-38-33-32]:not(does-not-exist) { order: 35; } -button > [\:has\(.foo\)] { +[csstools-has-2q-39-38-38-33-32-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 36; } -[\:has\(.foo\)] > button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-2q-39-38-38-33-32]:not(does-not-exist) { order: 37; } -button,[\%20\:has\(.foo\)] { +button, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 38; } -[\:has\(.foo\)], button { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], button { order: 39; } -.🧑🏾‍🎤[\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 40; } -[\:has\(.foo\)].🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 41; } -.🧑🏾‍🎤 [\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 42; } -[\:has\(.foo\)] .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 43; } -.🧑🏾‍🎤 [\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 44; } -[\:has\(.foo\)] .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 45; } -.🧑🏾‍🎤+[\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 46; } -[\:has\(.foo\)]+.🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 47; } -.🧑🏾‍🎤 + [\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 48; } -[\:has\(.foo\)] + .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 49; } -.🧑🏾‍🎤~[\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 50; } -[\:has\(.foo\)]~.🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 51; } -.🧑🏾‍🎤 ~ [\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 52; } -[\:has\(.foo\)] ~ .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 53; } -.🧑🏾‍🎤>[\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 54; } -[\:has\(.foo\)]>.🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 55; } -.🧑🏾‍🎤 > [\:has\(.foo\)] { +[csstools-has-1a-16pq-17td-16po-188u-6bx-16po-186c-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 56; } -[\:has\(.foo\)] > .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1a-16pq-17td-16po-188u-6bx-16po-186c]:not(.does-not-exist) { order: 57; } -.🧑🏾‍🎤,[\%20\:has\(.foo\)] { +.🧑🏾‍🎤, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 58; } -[\:has\(.foo\)], .🧑🏾‍🎤 { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], .🧑🏾‍🎤 { order: 59; } -.foo[\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 60; } -[\:has\(.foo\)].foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1a-2u-33-33]:not(.does-not-exist) { order: 61; } -.foo [\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 62; } -[\:has\(.foo\)] .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1a-2u-33-33]:not(.does-not-exist) { order: 63; } -.foo [\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 64; } -[\:has\(.foo\)] .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1a-2u-33-33]:not(.does-not-exist) { order: 65; } -.foo+[\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 66; } -[\:has\(.foo\)]+.foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1a-2u-33-33]:not(.does-not-exist) { order: 67; } -.foo + [\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 68; } -[\:has\(.foo\)] + .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1a-2u-33-33]:not(.does-not-exist) { order: 69; } -.foo~[\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 70; } -[\:has\(.foo\)]~.foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1a-2u-33-33]:not(.does-not-exist) { order: 71; } -.foo ~ [\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 72; } -[\:has\(.foo\)] ~ .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1a-2u-33-33]:not(.does-not-exist) { order: 73; } -.foo>[\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 74; } -[\:has\(.foo\)]>.foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1a-2u-33-33]:not(.does-not-exist) { order: 75; } -.foo > [\:has\(.foo\)] { +[csstools-has-1a-2u-33-33-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 76; } -[\:has\(.foo\)] > .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1a-2u-33-33]:not(.does-not-exist) { order: 77; } -.foo,[\%20\:has\(.foo\)] { +.foo, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 78; } -[\:has\(.foo\)], .foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], .foo { order: 79; } -#foo[\:has\(.foo\)] { +[csstools-has-z-2u-33-33-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 80; } -[\:has\(.foo\)]#foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-z-2u-33-33]:not(#does-not-exist) { order: 81; } -#foo [\:has\(.foo\)] { +[csstools-has-z-2u-33-33-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 82; } -[\:has\(.foo\)] #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-z-2u-33-33]:not(#does-not-exist) { order: 83; } -#foo [\:has\(.foo\)] { +[csstools-has-z-2u-33-33-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 84; } -[\:has\(.foo\)] #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-z-2u-33-33]:not(#does-not-exist) { order: 85; } -#foo+[\:has\(.foo\)] { +[csstools-has-z-2u-33-33-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 86; } -[\:has\(.foo\)]+#foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-z-2u-33-33]:not(#does-not-exist) { order: 87; } -#foo + [\:has\(.foo\)] { +[csstools-has-z-2u-33-33-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 88; } -[\:has\(.foo\)] + #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-z-2u-33-33]:not(#does-not-exist) { order: 89; } -#foo~[\:has\(.foo\)] { +[csstools-has-z-2u-33-33-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 90; } -[\:has\(.foo\)]~#foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-z-2u-33-33]:not(#does-not-exist) { order: 91; } -#foo ~ [\:has\(.foo\)] { +[csstools-has-z-2u-33-33-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 92; } -[\:has\(.foo\)] ~ #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-z-2u-33-33]:not(#does-not-exist) { order: 93; } -#foo>[\:has\(.foo\)] { +[csstools-has-z-2u-33-33-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 94; } -[\:has\(.foo\)]>#foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-z-2u-33-33]:not(#does-not-exist) { order: 95; } -#foo > [\:has\(.foo\)] { +[csstools-has-z-2u-33-33-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(#does-not-exist) { order: 96; } -[\:has\(.foo\)] > #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-z-2u-33-33]:not(#does-not-exist) { order: 97; } -#foo,[\%20\:has\(.foo\)] { +#foo, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 98; } -[\:has\(.foo\)], #foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], #foo { order: 99; } -__foo[\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 100; } -[\:has\(.foo\)]__foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-2n-2n-2u-33-33]:not(does-not-exist) { order: 101; } -__foo [\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 102; } -[\:has\(.foo\)] __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-2n-2n-2u-33-33]:not(does-not-exist) { order: 103; } -__foo [\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 104; } -[\:has\(.foo\)] __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-2n-2n-2u-33-33]:not(does-not-exist) { order: 105; } -__foo+[\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 106; } -[\:has\(.foo\)]+__foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-2n-2n-2u-33-33]:not(does-not-exist) { order: 107; } -__foo + [\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 108; } -[\:has\(.foo\)] + __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-2n-2n-2u-33-33]:not(does-not-exist) { order: 109; } -__foo~[\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 110; } -[\:has\(.foo\)]~__foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-2n-2n-2u-33-33]:not(does-not-exist) { order: 111; } -__foo ~ [\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 112; } -[\:has\(.foo\)] ~ __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-2n-2n-2u-33-33]:not(does-not-exist) { order: 113; } -__foo>[\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 114; } -[\:has\(.foo\)]>__foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-2n-2n-2u-33-33]:not(does-not-exist) { order: 115; } -__foo > [\:has\(.foo\)] { +[csstools-has-2n-2n-2u-33-33-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 116; } -[\:has\(.foo\)] > __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-2n-2n-2u-33-33]:not(does-not-exist) { order: 117; } -__foo,[\%20\:has\(.foo\)] { +__foo, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 118; } -[\:has\(.foo\)], __foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], __foo { order: 119; } -:--foo[\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 120; } -[\:has\(.foo\)]:--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 121; } -:--foo [\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 122; } -[\:has\(.foo\)] :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 123; } -:--foo [\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 124; } -[\:has\(.foo\)] :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 125; } -:--foo+[\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 126; } -[\:has\(.foo\)]+:--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 127; } -:--foo + [\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 128; } -[\:has\(.foo\)] + :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 129; } -:--foo~[\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 130; } -[\:has\(.foo\)]~:--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 131; } -:--foo ~ [\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 132; } -[\:has\(.foo\)] ~ :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 133; } -:--foo>[\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 134; } -[\:has\(.foo\)]>:--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 135; } -:--foo > [\:has\(.foo\)] { +[csstools-has-1m-19-19-2u-33-33-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 136; } -[\:has\(.foo\)] > :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1m-19-19-2u-33-33]:not(.does-not-exist) { order: 137; } -:--foo,[\%20\:has\(.foo\)] { +:--foo, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 138; } -[\:has\(.foo\)], :--foo { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], :--foo { order: 139; } -[foo="baz"][\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 140; } -[\:has\(.foo\)][foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 141; } -[foo="baz"] [\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 142; } -[\:has\(.foo\)] [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 143; } -[foo="baz"] [\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 144; } -[\:has\(.foo\)] [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 145; } -[foo="baz"]+[\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 146; } -[\:has\(.foo\)]+[foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 147; } -[foo="baz"] + [\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 148; } -[\:has\(.foo\)] + [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 149; } -[foo="baz"]~[\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 150; } -[\:has\(.foo\)]~[foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 151; } -[foo="baz"] ~ [\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 152; } -[\:has\(.foo\)] ~ [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 153; } -[foo="baz"]>[\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 154; } -[\:has\(.foo\)]>[foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 155; } -[foo="baz"] > [\:has\(.foo\)] { +[csstools-has-2j-2u-33-33-1p-y-2q-2p-3e-y-2l-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 156; } -[\:has\(.foo\)] > [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-2j-2u-33-33-1p-y-2q-2p-3e-y-2l]:not(.does-not-exist) { order: 157; } -[foo="baz"],[\%20\:has\(.foo\)] { +[foo="baz"], [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 158; } -[\:has\(.foo\)], [foo="baz"] { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], [foo="baz"] { order: 159; } -*[\:has\(.foo\)] { +[csstools-has-16-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 160; } -[\:has\(.foo\)]* { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-16] { order: 161; } -* [\:has\(.foo\)] { +[csstools-has-16-w-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 162; } -[\:has\(.foo\)] * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-16] { order: 163; } -* [\:has\(.foo\)] { +[csstools-has-16-w-w-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 164; } -[\:has\(.foo\)] * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-16] { order: 165; } -*+[\:has\(.foo\)] { +[csstools-has-16-17-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 166; } -[\:has\(.foo\)]+* { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-16] { order: 167; } -* + [\:has\(.foo\)] { +[csstools-has-16-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 168; } -[\:has\(.foo\)] + * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-16] { order: 169; } -*~[\:has\(.foo\)] { +[csstools-has-16-3i-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 170; } -[\:has\(.foo\)]~* { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-16] { order: 171; } -* ~ [\:has\(.foo\)] { +[csstools-has-16-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 172; } -[\:has\(.foo\)] ~ * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-16] { order: 173; } -*>[\:has\(.foo\)] { +[csstools-has-16-1q-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 174; } -[\:has\(.foo\)]>* { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-16] { order: 175; } -* > [\:has\(.foo\)] { +[csstools-has-16-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 176; } -[\:has\(.foo\)] > * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-16] { order: 177; } -*,[\%20\:has\(.foo\)] { +*, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 178; } -[\:has\(.foo\)], * { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], * { order: 179; } -:hover[\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 180; } -[\:has\(.foo\)]:hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 181; } -:hover [\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 182; } -[\:has\(.foo\)] :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 183; } -:hover [\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 184; } -[\:has\(.foo\)] :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 185; } -:hover+[\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 186; } -[\:has\(.foo\)]+:hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 187; } -:hover + [\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 188; } -[\:has\(.foo\)] + :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 189; } -:hover~[\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 190; } -[\:has\(.foo\)]~:hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 191; } -:hover ~ [\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 192; } -[\:has\(.foo\)] ~ :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 193; } -:hover>[\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 194; } -[\:has\(.foo\)]>:hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 195; } -:hover > [\:has\(.foo\)] { +[csstools-has-1m-2w-33-3a-2t-36-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 196; } -[\:has\(.foo\)] > :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1m-2w-33-3a-2t-36]:not(.does-not-exist) { order: 197; } -:hover,[\%20\:has\(.foo\)] { +:hover, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 198; } -[\:has\(.foo\)], :hover { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], :hover { order: 199; } -::before[\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 200; } -[\:has\(.foo\)]::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 201; } -::before [\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 202; } -[\:has\(.foo\)] ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 203; } -::before [\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-w-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 204; } -[\:has\(.foo\)] ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-w-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 205; } -::before+[\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-17-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 206; } -[\:has\(.foo\)]+::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-17-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 207; } -::before + [\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-w-17-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 208; } -[\:has\(.foo\)] + ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-17-w-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 209; } -::before~[\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-3i-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 210; } -[\:has\(.foo\)]~::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-3i-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 211; } -::before ~ [\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-w-3i-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 212; } -[\:has\(.foo\)] ~ ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-3i-w-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 213; } -::before>[\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-1q-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 214; } -[\:has\(.foo\)]>::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-1q-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 215; } -::before > [\:has\(.foo\)] { +[csstools-has-1m-1m-2q-2t-2u-33-36-2t-w-1q-w-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 216; } -[\:has\(.foo\)] > ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15-w-1q-w-1m-1m-2q-2t-2u-33-36-2t]:not(does-not-exist) { order: 217; } -::before,[\%20\:has\(.foo\)] { +::before, [csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15] { order: 218; } -[\:has\(.foo\)], ::before { +[csstools-has-1m-2w-2p-37-14-1a-2u-33-33-15], ::before { order: 219; } @@ -886,7 +886,7 @@ foo[baz=":has(.foo)"] { order: 221; } -[\:not-has\(.foo\)] { +[csstools-has-1m-32-33-38-14-1m-2w-2p-37-14-1a-2u-33-33-15-15] { order: 222; } @@ -894,11 +894,11 @@ foo[baz=":has(.foo)"] { order: 223; } -:--[\:has\(.foo\)] { +[csstools-has-1m-19-19-1m-2w-2p-37-14-1a-2u-33-33-15]:not(.does-not-exist) { order: 224; } -__[\:has\(.foo\)] { +[csstools-has-2n-2n-1m-2w-2p-37-14-1a-2u-33-33-15]:not(does-not-exist) { order: 225; } diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.after.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.after.expect.css new file mode 100644 index 000000000..812368834 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-logical.after.expect.css @@ -0,0 +1,6 @@ +[csstools-has-2j-2s-2x-36-1p-y-30-38-36-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px +} +[csstools-has-2j-2s-2x-36-1p-y-36-38-30-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px +} diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css new file mode 100644 index 000000000..f9c97002f --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css @@ -0,0 +1,57 @@ +[csstools-has-2j-2s-2x-36-1p-y-30-38-36-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px; +} +[dir="ltr"] a:has(.b) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15-1m-2s-2x-36-14-30-38-36-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px; +} +a:has(.b):dir(ltr) { + margin-left: 2px; +} +[csstools-has-2j-2s-2x-36-1p-y-36-38-30-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px; +} +[dir="rtl"] a:has(.b) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15-1m-2s-2x-36-14-36-38-30-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px; +} +a:has(.b):dir(rtl) { + margin-right: 2px; +} +[dir="ltr"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(ltr) { + margin-left: 2px; +} +[dir="rtl"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(rtl) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-inline-start: 2px; +} +[dir="ltr"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(ltr) { + margin-left: 2px; +} +[dir="rtl"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(rtl) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-inline-start: 2px; +} +a:has(.b) { + margin-inline-start: 2px; +} diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.before.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.before.expect.css new file mode 100644 index 000000000..812368834 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-logical.before.expect.css @@ -0,0 +1,6 @@ +[csstools-has-2j-2s-2x-36-1p-y-30-38-36-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px +} +[csstools-has-2j-2s-2x-36-1p-y-36-38-30-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px +} diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css new file mode 100644 index 000000000..f9c97002f --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css @@ -0,0 +1,57 @@ +[csstools-has-2j-2s-2x-36-1p-y-30-38-36-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px; +} +[dir="ltr"] a:has(.b) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15-1m-2s-2x-36-14-30-38-36-15]:not(.does-not-exist):not(does-not-exist) { + margin-left: 2px; +} +a:has(.b):dir(ltr) { + margin-left: 2px; +} +[csstools-has-2j-2s-2x-36-1p-y-36-38-30-y-2l-w-2p-1m-2w-2p-37-14-1a-2q-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px; +} +[dir="rtl"] a:has(.b) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15-1m-2s-2x-36-14-36-38-30-15]:not(.does-not-exist):not(does-not-exist) { + margin-right: 2px; +} +a:has(.b):dir(rtl) { + margin-right: 2px; +} +[dir="ltr"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(ltr) { + margin-left: 2px; +} +[dir="rtl"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(rtl) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-inline-start: 2px; +} +[dir="ltr"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-left: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(ltr) { + margin-left: 2px; +} +[dir="rtl"] [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist):dir(rtl) { + margin-right: 2px; +} +[csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + margin-inline-start: 2px; +} +a:has(.b) { + margin-inline-start: 2px; +} diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.css b/plugins/css-has-pseudo/test/plugin-order-logical.css new file mode 100644 index 000000000..e0742cbea --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-logical.css @@ -0,0 +1,3 @@ +a:has(.b) { + margin-inline-start: 2px; +} diff --git a/plugins/css-has-pseudo/test/plugin-order-nesting.after.expect.css b/plugins/css-has-pseudo/test/plugin-order-nesting.after.expect.css new file mode 100644 index 000000000..cce9158a8 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-nesting.after.expect.css @@ -0,0 +1,12 @@ + + [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + order: 1; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-1m-2u-33-2r-39-37]:not(does-not-exist):not(does-not-exist) { + order: 2; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-w-1m-2w-2p-37-14-1q-w-1a-37-33-31-2t-15]:not(does-not-exist):not(does-not-exist) { + order: 3; + } diff --git a/plugins/css-has-pseudo/test/plugin-order-nesting.after.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-nesting.after.preserve.expect.css new file mode 100644 index 000000000..76dffbb29 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-nesting.after.preserve.expect.css @@ -0,0 +1,24 @@ + + [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + order: 1; + } + +a:has(.b) { + order: 1; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-1m-2u-33-2r-39-37]:not(does-not-exist):not(does-not-exist) { + order: 2; + } + +a:has(> img):focus { + order: 2; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-w-1m-2w-2p-37-14-1q-w-1a-37-33-31-2t-15]:not(does-not-exist):not(does-not-exist) { + order: 3; + } + +a:has(> img) :has(> .some) { + order: 3; + } diff --git a/plugins/css-has-pseudo/test/plugin-order-nesting.before.expect.css b/plugins/css-has-pseudo/test/plugin-order-nesting.before.expect.css new file mode 100644 index 000000000..cce9158a8 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-nesting.before.expect.css @@ -0,0 +1,12 @@ + + [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + order: 1; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-1m-2u-33-2r-39-37]:not(does-not-exist):not(does-not-exist) { + order: 2; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-w-1m-2w-2p-37-14-1q-w-1a-37-33-31-2t-15]:not(does-not-exist):not(does-not-exist) { + order: 3; + } diff --git a/plugins/css-has-pseudo/test/plugin-order-nesting.before.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-nesting.before.preserve.expect.css new file mode 100644 index 000000000..76dffbb29 --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-nesting.before.preserve.expect.css @@ -0,0 +1,24 @@ + + [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { + order: 1; + } + +a:has(.b) { + order: 1; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-1m-2u-33-2r-39-37]:not(does-not-exist):not(does-not-exist) { + order: 2; + } + +a:has(> img):focus { + order: 2; + } + +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15-w-1m-2w-2p-37-14-1q-w-1a-37-33-31-2t-15]:not(does-not-exist):not(does-not-exist) { + order: 3; + } + +a:has(> img) :has(> .some) { + order: 3; + } diff --git a/plugins/css-has-pseudo/test/plugin-order-nesting.css b/plugins/css-has-pseudo/test/plugin-order-nesting.css new file mode 100644 index 000000000..b48879b8f --- /dev/null +++ b/plugins/css-has-pseudo/test/plugin-order-nesting.css @@ -0,0 +1,17 @@ +a { + &:has(.b) { + order: 1; + } +} + +a:has(> img) { + &:focus { + order: 2; + } +} + +a:has(> img) { + & :has(> .some) { + order: 3; + } +} diff --git a/plugins/css-has-pseudo/tsconfig.json b/plugins/css-has-pseudo/tsconfig.json new file mode 100644 index 000000000..e0d06239c --- /dev/null +++ b/plugins/css-has-pseudo/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} From 40c53382fde36be72f3bdf78b772646bea03fcb9 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 14 Jun 2022 18:08:20 +0200 Subject: [PATCH 02/11] preset-env --- .../postcss-preset-env/test/basic.autoprefixer.expect.css | 2 +- .../postcss-preset-env/test/basic.autoprefixer.false.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.ch38.expect.css | 2 +- .../postcss-preset-env/test/basic.ch88-ff78-saf10.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.ch88-ff78.expect.css | 2 +- .../test/basic.ch88-ff78.no-is-pseudo.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.ff49.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.ff66.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.ie10.expect.css | 2 +- .../postcss-preset-env/test/basic.nesting.false.expect.css | 2 +- .../postcss-preset-env/test/basic.preserve.true.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.safari15.expect.css | 2 +- .../postcss-preset-env/test/basic.stage0-ff49.expect.css | 2 +- .../postcss-preset-env/test/basic.stage0-ff66.expect.css | 2 +- plugin-packs/postcss-preset-env/test/basic.stage0.expect.css | 2 +- .../test/client-side-polyfills.stage-1.expect.css | 2 +- .../test/client-side-polyfills.stage-2.expect.css | 2 +- .../test/disable-client-side-polyfills.disabled.expect.css | 2 +- plugin-packs/postcss-preset-env/test/layers-basic.expect.css | 2 +- .../test/layers-basic.preserve.true.expect.css | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/plugin-packs/postcss-preset-env/test/basic.autoprefixer.expect.css b/plugin-packs/postcss-preset-env/test/basic.autoprefixer.expect.css index 23aba44e8..25858750c 100644 --- a/plugin-packs/postcss-preset-env/test/basic.autoprefixer.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.autoprefixer.expect.css @@ -269,7 +269,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.autoprefixer.false.expect.css b/plugin-packs/postcss-preset-env/test/basic.autoprefixer.false.expect.css index 23aba44e8..25858750c 100644 --- a/plugin-packs/postcss-preset-env/test/basic.autoprefixer.false.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.autoprefixer.false.expect.css @@ -269,7 +269,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ch38.expect.css b/plugin-packs/postcss-preset-env/test/basic.ch38.expect.css index 42d22f1dd..8662f17da 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ch38.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ch38.expect.css @@ -189,7 +189,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78-saf10.expect.css b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78-saf10.expect.css index a3241a7f6..880700416 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78-saf10.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78-saf10.expect.css @@ -188,7 +188,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.expect.css b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.expect.css index 56d965fd9..26d718e8f 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.expect.css @@ -181,7 +181,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.no-is-pseudo.expect.css b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.no-is-pseudo.expect.css index b24f544ea..0e2d532ef 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.no-is-pseudo.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ch88-ff78.no-is-pseudo.expect.css @@ -181,7 +181,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.expect.css b/plugin-packs/postcss-preset-env/test/basic.expect.css index e2e50b72d..59aac48e7 100644 --- a/plugin-packs/postcss-preset-env/test/basic.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.expect.css @@ -293,7 +293,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ff49.expect.css b/plugin-packs/postcss-preset-env/test/basic.ff49.expect.css index 73df8d7c5..34b0a3fa7 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ff49.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ff49.expect.css @@ -185,7 +185,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ff66.expect.css b/plugin-packs/postcss-preset-env/test/basic.ff66.expect.css index 68ba13244..37806912a 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ff66.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ff66.expect.css @@ -173,7 +173,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.ie10.expect.css b/plugin-packs/postcss-preset-env/test/basic.ie10.expect.css index 7342e4f3c..ecbf0b07b 100644 --- a/plugin-packs/postcss-preset-env/test/basic.ie10.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.ie10.expect.css @@ -301,7 +301,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.nesting.false.expect.css b/plugin-packs/postcss-preset-env/test/basic.nesting.false.expect.css index 285f313a5..84174f7aa 100644 --- a/plugin-packs/postcss-preset-env/test/basic.nesting.false.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.nesting.false.expect.css @@ -291,7 +291,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.preserve.true.expect.css b/plugin-packs/postcss-preset-env/test/basic.preserve.true.expect.css index ba8a74cd5..108e26d10 100644 --- a/plugin-packs/postcss-preset-env/test/basic.preserve.true.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.preserve.true.expect.css @@ -524,7 +524,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.safari15.expect.css b/plugin-packs/postcss-preset-env/test/basic.safari15.expect.css index 73a8f5820..725600ff5 100644 --- a/plugin-packs/postcss-preset-env/test/basic.safari15.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.safari15.expect.css @@ -157,7 +157,7 @@ background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.stage0-ff49.expect.css b/plugin-packs/postcss-preset-env/test/basic.stage0-ff49.expect.css index 79d549c1d..22d001644 100644 --- a/plugin-packs/postcss-preset-env/test/basic.stage0-ff49.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.stage0-ff49.expect.css @@ -190,7 +190,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.stage0-ff66.expect.css b/plugin-packs/postcss-preset-env/test/basic.stage0-ff66.expect.css index f89a68387..ba8351dd1 100644 --- a/plugin-packs/postcss-preset-env/test/basic.stage0-ff66.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.stage0-ff66.expect.css @@ -178,7 +178,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.stage0.expect.css b/plugin-packs/postcss-preset-env/test/basic.stage0.expect.css index 2ed948f70..dc08ca7eb 100644 --- a/plugin-packs/postcss-preset-env/test/basic.stage0.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.stage0.expect.css @@ -298,7 +298,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-1.expect.css b/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-1.expect.css index b6e6b4162..ba171663b 100644 --- a/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-1.expect.css +++ b/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-1.expect.css @@ -10,7 +10,7 @@ order: 3; } -[\:has\(.a\)] { +[csstools-has-1m-2w-2p-37-14-1a-2p-15] { order: 4; } diff --git a/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-2.expect.css b/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-2.expect.css index 92d3a8efd..f72e02255 100644 --- a/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-2.expect.css +++ b/plugin-packs/postcss-preset-env/test/client-side-polyfills.stage-2.expect.css @@ -10,7 +10,7 @@ order: 3; } -[\:has\(.a\)] { +[csstools-has-1m-2w-2p-37-14-1a-2p-15] { order: 4; } diff --git a/plugin-packs/postcss-preset-env/test/disable-client-side-polyfills.disabled.expect.css b/plugin-packs/postcss-preset-env/test/disable-client-side-polyfills.disabled.expect.css index 64a31a360..1e7ff7369 100644 --- a/plugin-packs/postcss-preset-env/test/disable-client-side-polyfills.disabled.expect.css +++ b/plugin-packs/postcss-preset-env/test/disable-client-side-polyfills.disabled.expect.css @@ -6,7 +6,7 @@ input:blank { order: 1; } -a[\:has\(\%3E\%20img\)] { +[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) { order: 2; } diff --git a/plugin-packs/postcss-preset-env/test/layers-basic.expect.css b/plugin-packs/postcss-preset-env/test/layers-basic.expect.css index ec3e575b5..c14329adb 100644 --- a/plugin-packs/postcss-preset-env/test/layers-basic.expect.css +++ b/plugin-packs/postcss-preset-env/test/layers-basic.expect.css @@ -474,7 +474,7 @@ h1.test-custom-selectors:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):n background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) { background-color: yellow; } diff --git a/plugin-packs/postcss-preset-env/test/layers-basic.preserve.true.expect.css b/plugin-packs/postcss-preset-env/test/layers-basic.preserve.true.expect.css index b955c29a2..acc82360c 100644 --- a/plugin-packs/postcss-preset-env/test/layers-basic.preserve.true.expect.css +++ b/plugin-packs/postcss-preset-env/test/layers-basic.preserve.true.expect.css @@ -535,7 +535,7 @@ h1.test-custom-selectors:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):n background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) { background-color: yellow; } From 771a6aedc92b903de4145a0f147699f546f2e317 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 18 Jun 2022 22:29:41 +0200 Subject: [PATCH 03/11] update tests --- plugin-packs/postcss-preset-env/test/basic.op_mini.expect.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-packs/postcss-preset-env/test/basic.op_mini.expect.css b/plugin-packs/postcss-preset-env/test/basic.op_mini.expect.css index 2b937e1c0..fe2bbdec3 100644 --- a/plugin-packs/postcss-preset-env/test/basic.op_mini.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.op_mini.expect.css @@ -276,7 +276,7 @@ h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.te background-color: yellow; } -.test-has-pseudo-class[\:has\(.inner-class\)] { +[csstools-has-1a-38-2t-37-38-19-2w-2p-37-19-34-37-2t-39-2s-33-19-2r-30-2p-37-37-1m-2w-2p-37-14-1a-2x-32-32-2t-36-19-2r-30-2p-37-37-15]:not(.does-not-exist) { background-color: yellow; } From d1bdec8152022e9865746dec1f19a8c816d4bef9 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 18 Jun 2022 22:52:40 +0200 Subject: [PATCH 04/11] fixes --- package-lock.json | 4 +- plugins/css-has-pseudo/package.json | 3 +- plugins/css-has-pseudo/src/index.ts | 24 ++++---- .../src/is-guarded-by-at-supports.ts | 56 +++++++++++++++++++ plugins/css-has-pseudo/test/basic.css | 6 ++ plugins/css-has-pseudo/test/basic.expect.css | 6 ++ .../test/basic.preserve.expect.css | 6 ++ ...basic.specificity-matching-name.expect.css | 6 ++ 8 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 plugins/css-has-pseudo/src/is-guarded-by-at-supports.ts diff --git a/package-lock.json b/package-lock.json index 8ab6a4b47..019234a8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6776,7 +6776,8 @@ "license": "CC0-1.0", "dependencies": { "@csstools/selector-specificity": "^2.0.1", - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0" }, "devDependencies": { "@mrhenry/core-web": "^0.7.2", @@ -9752,6 +9753,7 @@ "@csstools/selector-specificity": "^2.0.1", "@mrhenry/core-web": "^0.7.2", "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0", "puppeteer": "^13.6.0" } }, diff --git a/plugins/css-has-pseudo/package.json b/plugins/css-has-pseudo/package.json index bf24a02f7..38fd2e9d3 100644 --- a/plugins/css-has-pseudo/package.json +++ b/plugins/css-has-pseudo/package.json @@ -51,7 +51,8 @@ ], "dependencies": { "@csstools/selector-specificity": "^2.0.1", - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" diff --git a/plugins/css-has-pseudo/src/index.ts b/plugins/css-has-pseudo/src/index.ts index cabc31012..f852e6eb1 100644 --- a/plugins/css-has-pseudo/src/index.ts +++ b/plugins/css-has-pseudo/src/index.ts @@ -1,7 +1,8 @@ +import type { PluginCreator } from 'postcss'; import parser from 'postcss-selector-parser'; import { selectorSpecificity } from '@csstools/selector-specificity'; import encodeCSS from './encode/encode.mjs'; -import type { PluginCreator } from 'postcss'; +import { isGuardedByAtSupportsFromAtRuleParams } from './is-guarded-by-at-supports.js'; const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: string }> = (opts?: { preserve?: boolean, specificityMatchingName?: string }) => { const options = { @@ -90,10 +91,10 @@ const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: str return; } - if (options.preserve) { - rule.cloneBefore({ selectors: selectors }); - } else { - rule.selectors = selectors; + rule.cloneBefore({ selectors: selectors }); + + if (!options.preserve) { + rule.remove(); } }, }; @@ -104,17 +105,18 @@ creator.postcss = true; export default creator; function isWithinSupportCheck(rule) { - let isSupportCheck = false; let ruleParent = rule.parent; - while (!isSupportCheck && ruleParent) { - if (ruleParent.type === 'atrule') { - - isSupportCheck = ruleParent.params.includes(':has(') && ruleParent.params.startsWith('selector('); + while (ruleParent) { + if ( + ruleParent.type === 'atrule' && + isGuardedByAtSupportsFromAtRuleParams(ruleParent.params) + ) { + return true; } ruleParent = ruleParent.parent; } - return isSupportCheck; + return false; } diff --git a/plugins/css-has-pseudo/src/is-guarded-by-at-supports.ts b/plugins/css-has-pseudo/src/is-guarded-by-at-supports.ts new file mode 100644 index 000000000..58ee65adc --- /dev/null +++ b/plugins/css-has-pseudo/src/is-guarded-by-at-supports.ts @@ -0,0 +1,56 @@ +import valueParser from 'postcss-value-parser'; +import selectorParser from 'postcss-selector-parser'; + +export function isGuardedByAtSupportsFromAtRuleParams(atSupportsParams: string): boolean { + if (!atSupportsParams.includes(':has(')) { + return false; + } + + let isGuardedByAtSupports = false; + + try { + const selectors: Set = new Set(); + + const ast = valueParser(atSupportsParams); + ast.walk((node) => { + if (node.type === 'function' && node.value === 'selector') { + selectors.add(valueParser.stringify(node.nodes)); + return false; + } + }); + + selectors.forEach((selector) => { + if (selectorContainsHasPseudo(selector)) { + isGuardedByAtSupports = true; + } + }); + + } catch (e) { + /* ignore */ + } + + return isGuardedByAtSupports; +} + +export function selectorContainsHasPseudo(selector: string): boolean { + if (!selector.includes(':has(')) { + return false; + } + + let containsHasPseudo = false; + + try { + const ast = selectorParser().astSync(selector); + ast.walk((node) => { + if (node.type === 'pseudo' && node.value === ':has' && node.nodes && node.nodes.length > 0) { + containsHasPseudo = true; + return false; + } + }); + + } catch (e) { + /* ignore */ + } + + return containsHasPseudo; +} diff --git a/plugins/css-has-pseudo/test/basic.css b/plugins/css-has-pseudo/test/basic.css index e37868806..35908d506 100644 --- a/plugins/css-has-pseudo/test/basic.css +++ b/plugins/css-has-pseudo/test/basic.css @@ -142,3 +142,9 @@ body:not(:has(:focus)) { order: 33; } } + +@supports (display: grid) and selector( :has( :focus)) { + :has(:focus) { + order: 34; + } +} diff --git a/plugins/css-has-pseudo/test/basic.expect.css b/plugins/css-has-pseudo/test/basic.expect.css index 052c39c82..7f93d010b 100644 --- a/plugins/css-has-pseudo/test/basic.expect.css +++ b/plugins/css-has-pseudo/test/basic.expect.css @@ -273,3 +273,9 @@ body:not(:has(:focus)) { order: 33; } } + +@supports (display: grid) and selector( :has( :focus)) { + :has(:focus) { + order: 34; + } +} diff --git a/plugins/css-has-pseudo/test/basic.preserve.expect.css b/plugins/css-has-pseudo/test/basic.preserve.expect.css index 053bc256d..f73775859 100644 --- a/plugins/css-has-pseudo/test/basic.preserve.expect.css +++ b/plugins/css-has-pseudo/test/basic.preserve.expect.css @@ -142,3 +142,9 @@ order: 33; } } + +@supports (display: grid) and selector( :has( :focus)) { + :has(:focus) { + order: 34; + } +} diff --git a/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css b/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css index cbc4287ec..796416e89 100644 --- a/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css +++ b/plugins/css-has-pseudo/test/basic.specificity-matching-name.expect.css @@ -273,3 +273,9 @@ body:not(:has(:focus)) { order: 33; } } + +@supports (display: grid) and selector( :has( :focus)) { + :has(:focus) { + order: 34; + } +} From 2f7c8e7d5dc04b3384f95c9a63c5f535f9cb0ca1 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:03:42 +0200 Subject: [PATCH 05/11] docs --- .github/bin/generate-docs/readme.mjs | 2 + plugins/css-has-pseudo/README.md | 244 +++++++++++++++++- plugins/css-has-pseudo/docs/README.md | 177 ++++++++++++- plugins/css-prefers-color-scheme/README.md | 9 +- .../css-prefers-color-scheme/docs/README.md | 18 +- .../docs/README.md | 1 + plugins/postcss-base-plugin/docs/README.md | 1 + plugins/postcss-cascade-layers/docs/README.md | 1 + plugins/postcss-color-function/docs/README.md | 1 + .../postcss-color-hex-alpha/docs/README.md | 1 + .../docs/README.md | 1 + plugins/postcss-custom-media/docs/README.md | 1 + .../postcss-custom-selectors/docs/README.md | 1 + plugins/postcss-design-tokens/docs/README.md | 1 + .../docs/README.md | 1 + plugins/postcss-selector-not/docs/README.md | 1 + .../docs/README.md | 1 + .../docs/README.md | 1 + 18 files changed, 443 insertions(+), 20 deletions(-) diff --git a/.github/bin/generate-docs/readme.mjs b/.github/bin/generate-docs/readme.mjs index 97251424c..8a9fe9b8d 100644 --- a/.github/bin/generate-docs/readme.mjs +++ b/.github/bin/generate-docs/readme.mjs @@ -15,6 +15,7 @@ readmeDoc = readmeDoc.replace(` + @@ -90,6 +91,7 @@ readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.csstools.cssdbId); readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.csstools.exportName); readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.csstools.humanReadableName); readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.name); +readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.version); readmeDoc = readmeDoc.replaceAll('', path.join(path.basename(path.dirname(process.cwd())), path.basename(process.cwd()))); readmeDoc = readmeDoc.replaceAll('', packageJSONInfo.csstools.specUrl); diff --git a/plugins/css-has-pseudo/README.md b/plugins/css-has-pseudo/README.md index 62ed16a75..3700459fd 100644 --- a/plugins/css-has-pseudo/README.md +++ b/plugins/css-has-pseudo/README.md @@ -2,19 +2,19 @@ [npm version][npm-url] [CSS Standard Status][css-url] [Build Status][cli-url] [Discord][discord] -[PostCSS Has Pseudo] lets you easily create new plugins following some [CSS Specification]. +[PostCSS Has Pseudo] lets you style elements relative to other elements in CSS, following the [Selectors Level 4] specification. ```pcss -h1:has(+ p) { +.title:has(+ p) { margin-bottom: 1.5rem; } /* becomes */ -[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { +[csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { margin-bottom: 1.5rem; } -h1:has(+ p) { +.title:has(+ p) { margin-bottom: 1.5rem; } ``` @@ -56,20 +56,248 @@ postcssHasPseudo({ preserve: true }) ``` ```pcss -h1:has(+ p) { +.title:has(+ p) { margin-bottom: 1.5rem; } /* becomes */ -[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { +[csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { margin-bottom: 1.5rem; } -h1:has(+ p) { +.title:has(+ p) { margin-bottom: 1.5rem; } ``` +### specificityMatchingName + +The `specificityMatchingName` option allows you to change to selector that is used to adjust specificity. +The default value is `does-not-exist`. +If this is an actual class, id or tag name in your code, you will need to set a different option here. + +See how `:not` is used to modify [specificity](#specificity). + +```js +postcssHasPseudo({ specificityMatchingName: 'something-random' }) +``` + +[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=.x%3Ahas(%3E%20%23a%3Ahover)) + +Before : + +```css +.x:has(> #a:hover) { + order: 11; +} +``` + +After : + +[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15%5D%3Anot(%23does-not-exist)%3Anot(.does-not-exist)) + +```css +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { + order: 11; +} +``` + +## ⚠️ Known shortcomings + +### Specificity + +`:has` transforms will result in at least one tag selector with specificity `0, 1, 0`. +If your selector has only tags we won't be able to match the original specificity. + +Before : + +[specificity 0, 0, 2](https://polypane.app/css-specificity-calculator/#selector=figure%3Ahas(%3E%20img)) + +```css +figure:has(> img) +``` + +After : + +[specificity 0, 1, 2](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15%5D%3Anot(does-not-exist)%3Anot(does-not-exist)) + +```css +[csstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) +``` + +### Plugin order + +As selectors are encoded, this plugin (or `postcss-preset-env`) must be run after any other plugin that transforms selectors. + +If other plugins are used, you need to place these in your config before `postcss-preset-env` or `css-has-pseudo`. + +Please let us know if you have issues with plugins that transform selectors. +Then we can investigate and maybe fix these. + +## Browser + +```js +// initialize prefersColorScheme (applies the current OS color scheme, if available) +import cssHasPseudo from 'css-has-pseudo/browser'; +cssHasPseudo(document); +``` + +or + +```html + + + +``` + +⚠️ Please use a versioned url, like this : `https://unpkg.com/css-has-pseudo@3.0.4/dist/browser-global.js` +Without the version, you might unexpectedly get a new major version of the library with breaking changes. + +[PostCSS Has Pseudo] works in all major browsers, including +Internet Explorer 11. With a [Mutation Observer polyfill](https://github.com/webmodules/mutation-observer), the script will work +down to Internet Explorer 9. + +### Browser Usage + +#### hover + +The `hover` option determines if `:hover` pseudo-class should be tracked. +This is disabled by default because it is an expensive operation. + +```js +cssHasPseudo(document, { hover: true }); +``` + +#### observedAttributes + +The `observedAttributes` option determines which html attributes are observed. +If you do any client side modification of non-standard attributes and use these in combination with `:has()` you should add these here. + +```js +cssHasPseudo(document, { observedAttributes: ['something-not-standard'] }); +``` + +#### forcePolyfill + +The `forcePolyfill` option determines if the polyfill is used even when the browser has native support. +This is needed when you set `preserve: false` in the PostCSS plugin config. + +```js +cssHasPseudo(document, { forcePolyfill: true }); +``` + +#### debug + +The `debug` option determines if errors are emitted to the console in browser. +By default the polyfill will not emit errors or warnings. + +```js +cssHasPseudo(document, { debug: true }); +``` + + +### Browser Dependencies + +Web API's: + +- [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) +- [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) +- [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) with support for post CSS 2.1 selectors and `:scope` selectors. + +ECMA Script: + +- `Array.prototype.filter` +- `Array.prototype.forEach` +- `Array.prototype.indexOf` +- `Array.prototype.join` +- `Array.prototype.map` +- `Array.prototype.splice` +- `RegExp.prototype.exec` +- `String.prototype.match` +- `String.prototype.replace` +- `String.prototype.split` + +## CORS + +⚠️ Applies to you if you load CSS from a different domain than the page. + +In this case the CSS is treated as untrusted and will not be made available to the JavaScript polyfill. +The polyfill will not work without applying the correct configuration for CORS. + +Example : + +| page | css | CORS applies | +| --- | --- | --- | +| https://example.com/ | https://example.com/style.css | no | +| https://example.com/ | https://other.com/style.css | yes | + + +**You might see one of these error messages :** + +Chrome : + +> DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules + +Safari : + +> SecurityError: Not allowed to access cross-origin stylesheet + +Firefox : + +> DOMException: CSSStyleSheet.cssRules getter: Not allowed to access cross-origin stylesheet + +To resolve CORS errors you need to take two steps : + +- add an HTTP header `Access-Control-Allow-Origin: ` when serving your CSS file. +- add `crossorigin="anonymous"` to the `` tag for your CSS file. + +In a node server setting the HTTP header might look like this : + +```js +// http://localhost:8080 is the domain of your page! +res.setHeader('Access-Control-Allow-Origin', 'https://example.com'); +``` + +You can also configure a wildcard but please be aware that this might be a security risk. +It is better to only set the header for the domain you want to allow and only on the responses you want to allow. + +HTML might look like this : + +```html + +``` + + +## How it works + +The [PostCSS Has Pseudo] clones rules containing `:has()`, +replacing them with an alternative `[csstools-has-]` selector. + +```pcss +.title:has(+ p) { + margin-bottom: 1.5rem; +} + +/* becomes */ + +[csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { + margin-bottom: 1.5rem; +} +.title:has(+ p) { + margin-bottom: 1.5rem; +} +``` + +Next, the [browser script](#browser) adds a `[:has]` attribute to +elements otherwise matching `:has` natively. + +```html +
+

A title block

+

With an extra paragraph

+
+``` + [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test [css-url]: https://cssdb.org/#has-pseudo-class [discord]: https://discord.gg/bUadyRwkJS @@ -80,4 +308,4 @@ h1:has(+ p) { [PostCSS]: https://github.com/postcss/postcss [PostCSS Loader]: https://github.com/postcss/postcss-loader [PostCSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo -[CSS Specification]: https://www.w3.org/TR/selectors-4/#has-pseudo +[Selectors Level 4]: https://www.w3.org/TR/selectors-4/#has-pseudo diff --git a/plugins/css-has-pseudo/docs/README.md b/plugins/css-has-pseudo/docs/README.md index ffae28178..439462e47 100644 --- a/plugins/css-has-pseudo/docs/README.md +++ b/plugins/css-has-pseudo/docs/README.md @@ -2,6 +2,7 @@ + @@ -15,7 +16,7 @@
-[] lets you easily create new plugins following some [CSS Specification]. +[] lets you style elements relative to other elements in CSS, following the [Selectors Level 4] specification. ```pcss @@ -48,5 +49,177 @@ is preserved. By default, it is not preserved. ``` +### specificityMatchingName + +The `specificityMatchingName` option allows you to change to selector that is used to adjust specificity. +The default value is `does-not-exist`. +If this is an actual class, id or tag name in your code, you will need to set a different option here. + +See how `:not` is used to modify [specificity](#specificity). + +```js +({ specificityMatchingName: 'something-random' }) +``` + +[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=.x%3Ahas(%3E%20%23a%3Ahover)) + +Before : + +```css +.x:has(> #a:hover) { + order: 11; +} +``` + +After : + +[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15%5D%3Anot(%23does-not-exist)%3Anot(.does-not-exist)) + +```css +[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { + order: 11; +} +``` + +## ⚠️ Known shortcomings + +### Specificity + +`:has` transforms will result in at least one tag selector with specificity `0, 1, 0`. +If your selector has only tags we won't be able to match the original specificity. + +Before : + +[specificity 0, 0, 2](https://polypane.app/css-specificity-calculator/#selector=figure%3Ahas(%3E%20img)) + +```css +figure:has(> img) +``` + +After : + +[specificity 0, 1, 2](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15%5D%3Anot(does-not-exist)%3Anot(does-not-exist)) + +```css +[csstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) +``` + +### Plugin order + +As selectors are encoded, this plugin (or `postcss-preset-env`) must be run after any other plugin that transforms selectors. + +If other plugins are used, you need to place these in your config before `postcss-preset-env` or `css-has-pseudo`. + +Please let us know if you have issues with plugins that transform selectors. +Then we can investigate and maybe fix these. + +## Browser + +```js +// initialize prefersColorScheme (applies the current OS color scheme, if available) +import cssHasPseudo from '/browser'; +cssHasPseudo(document); +``` + +or + +```html + + + +``` + +⚠️ Please use a versioned url, like this : `https://unpkg.com/@/dist/browser-global.js` +Without the version, you might unexpectedly get a new major version of the library with breaking changes. + +[] works in all major browsers, including +Internet Explorer 11. With a [Mutation Observer polyfill](https://github.com/webmodules/mutation-observer), the script will work +down to Internet Explorer 9. + +### Browser Usage + +#### hover + +The `hover` option determines if `:hover` pseudo-class should be tracked. +This is disabled by default because it is an expensive operation. + +```js +cssHasPseudo(document, { hover: true }); +``` + +#### observedAttributes + +The `observedAttributes` option determines which html attributes are observed. +If you do any client side modification of non-standard attributes and use these in combination with `:has()` you should add these here. + +```js +cssHasPseudo(document, { observedAttributes: ['something-not-standard'] }); +``` + +#### forcePolyfill + +The `forcePolyfill` option determines if the polyfill is used even when the browser has native support. +This is needed when you set `preserve: false` in the PostCSS plugin config. + +```js +cssHasPseudo(document, { forcePolyfill: true }); +``` + +#### debug + +The `debug` option determines if errors are emitted to the console in browser. +By default the polyfill will not emit errors or warnings. + +```js +cssHasPseudo(document, { debug: true }); +``` + + +### Browser Dependencies + +Web API's: + +- [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) +- [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) +- [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) with support for post CSS 2.1 selectors and `:scope` selectors. + +ECMA Script: + +- `Array.prototype.filter` +- `Array.prototype.forEach` +- `Array.prototype.indexOf` +- `Array.prototype.join` +- `Array.prototype.map` +- `Array.prototype.splice` +- `RegExp.prototype.exec` +- `String.prototype.match` +- `String.prototype.replace` +- `String.prototype.split` + + + +## How it works + +The [] clones rules containing `:has()`, +replacing them with an alternative `[csstools-has-]` selector. + +```pcss + + +/* becomes */ + + +``` + +Next, the [browser script](#browser) adds a `[:has]` attribute to +elements otherwise matching `:has` natively. + +```html +
+

A title block

+

With an extra paragraph

+
+``` + -[CSS Specification]: +[Selectors Level 4]: diff --git a/plugins/css-prefers-color-scheme/README.md b/plugins/css-prefers-color-scheme/README.md index 948597d9e..88158f137 100644 --- a/plugins/css-prefers-color-scheme/README.md +++ b/plugins/css-prefers-color-scheme/README.md @@ -133,16 +133,19 @@ or ```html - + ``` +⚠️ Please use a versioned url, like this : `https://unpkg.com/css-prefers-color-scheme@6.0.3/dist/browser-global.js` +Without the version, you might unexpectedly get a new major version of the library with breaking changes. + [Prefers Color Scheme] works in all major browsers, including Safari 6+ and Internet Explorer 9+ without any additional polyfills. To maintain compatibility with browsers supporting `prefers-color-scheme`, the library will remove `prefers-color-scheme` media queries in favor of -cross-browser compatible `color` media queries. This ensures a seemless +cross-browser compatible `color` media queries. This ensures a seamless experience, even when JavaScript is unable to run. ### Browser Usage @@ -208,7 +211,7 @@ const prefersColorScheme = prefersColorSchemeInit('light', { debug: true }); ``` ```html - + ``` diff --git a/plugins/css-prefers-color-scheme/docs/README.md b/plugins/css-prefers-color-scheme/docs/README.md index 99b35bd72..bbe26804e 100644 --- a/plugins/css-prefers-color-scheme/docs/README.md +++ b/plugins/css-prefers-color-scheme/docs/README.md @@ -2,6 +2,7 @@ + @@ -63,21 +64,24 @@ or ```html - + ``` -[Prefers Color Scheme] works in all major browsers, including Safari 6+ and +⚠️ Please use a versioned url, like this : `https://unpkg.com/@/dist/browser-global.js` +Without the version, you might unexpectedly get a new major version of the library with breaking changes. + +[] works in all major browsers, including Safari 6+ and Internet Explorer 9+ without any additional polyfills. To maintain compatibility with browsers supporting `prefers-color-scheme`, the library will remove `prefers-color-scheme` media queries in favor of -cross-browser compatible `color` media queries. This ensures a seemless +cross-browser compatible `color` media queries. This ensures a seamless experience, even when JavaScript is unable to run. ### Browser Usage -Use [Prefers Color Scheme] to activate your `prefers-color-scheme` queries: +Use [] to activate your `prefers-color-scheme` queries: ```js import prefersColorSchemeInit from '/browser'; @@ -138,7 +142,7 @@ const prefersColorScheme = prefersColorSchemeInit('light', { debug: true }); ``` ```html - + ``` @@ -162,7 +166,7 @@ ECMA Script: ## How does it work? -[Prefers Color Scheme] is a [PostCSS] plugin that transforms `prefers-color-scheme` queries into `color` queries. +[] is a [PostCSS] plugin that transforms `prefers-color-scheme` queries into `color` queries. This changes `prefers-color-scheme: dark` into `(color: 48842621)` and `prefers-color-scheme: light` into `(color: 70318723)`. The frontend receives these `color` queries, which are understood in all @@ -170,7 +174,7 @@ major browsers going back to Internet Explorer 9. However, since browsers can only have a reasonably small number of bits per color, our color scheme values are ignored. -[Prefers Color Scheme] uses a [browser script](#browser) to change +[] uses a [browser script](#browser) to change `(color: 48842621)` queries into `(max-color: 48842621)` in order to activate “dark mode” specific CSS, and it changes `(color: 70318723)` queries into `(max-color: 48842621)` to activate “light mode” specific CSS. diff --git a/plugins/postcss-attribute-case-insensitive/docs/README.md b/plugins/postcss-attribute-case-insensitive/docs/README.md index 11ea089b5..1558b2e94 100644 --- a/plugins/postcss-attribute-case-insensitive/docs/README.md +++ b/plugins/postcss-attribute-case-insensitive/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-base-plugin/docs/README.md b/plugins/postcss-base-plugin/docs/README.md index ffae28178..000652492 100644 --- a/plugins/postcss-base-plugin/docs/README.md +++ b/plugins/postcss-base-plugin/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-cascade-layers/docs/README.md b/plugins/postcss-cascade-layers/docs/README.md index e23164c2e..3dc51167c 100644 --- a/plugins/postcss-cascade-layers/docs/README.md +++ b/plugins/postcss-cascade-layers/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-color-function/docs/README.md b/plugins/postcss-color-function/docs/README.md index d7781b42a..4eba0eda5 100644 --- a/plugins/postcss-color-function/docs/README.md +++ b/plugins/postcss-color-function/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-color-hex-alpha/docs/README.md b/plugins/postcss-color-hex-alpha/docs/README.md index b113e5491..a779afe50 100644 --- a/plugins/postcss-color-hex-alpha/docs/README.md +++ b/plugins/postcss-color-hex-alpha/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-color-rebeccapurple/docs/README.md b/plugins/postcss-color-rebeccapurple/docs/README.md index 4227a778b..60f923525 100644 --- a/plugins/postcss-color-rebeccapurple/docs/README.md +++ b/plugins/postcss-color-rebeccapurple/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-custom-media/docs/README.md b/plugins/postcss-custom-media/docs/README.md index 1d8e6059d..9961d39fd 100644 --- a/plugins/postcss-custom-media/docs/README.md +++ b/plugins/postcss-custom-media/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-custom-selectors/docs/README.md b/plugins/postcss-custom-selectors/docs/README.md index ee4121bf1..94e7d6e0f 100644 --- a/plugins/postcss-custom-selectors/docs/README.md +++ b/plugins/postcss-custom-selectors/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md index 620629758..767da4958 100644 --- a/plugins/postcss-design-tokens/docs/README.md +++ b/plugins/postcss-design-tokens/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-gradients-interpolation-method/docs/README.md b/plugins/postcss-gradients-interpolation-method/docs/README.md index 7fc7b7123..8928eecef 100644 --- a/plugins/postcss-gradients-interpolation-method/docs/README.md +++ b/plugins/postcss-gradients-interpolation-method/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-selector-not/docs/README.md b/plugins/postcss-selector-not/docs/README.md index 0e5b42d48..fd4050828 100644 --- a/plugins/postcss-selector-not/docs/README.md +++ b/plugins/postcss-selector-not/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-stepped-value-functions/docs/README.md b/plugins/postcss-stepped-value-functions/docs/README.md index 49052ea35..d27cfcc22 100644 --- a/plugins/postcss-stepped-value-functions/docs/README.md +++ b/plugins/postcss-stepped-value-functions/docs/README.md @@ -2,6 +2,7 @@ + diff --git a/plugins/postcss-trigonometric-functions/docs/README.md b/plugins/postcss-trigonometric-functions/docs/README.md index 547d76e5b..e1eb5981c 100644 --- a/plugins/postcss-trigonometric-functions/docs/README.md +++ b/plugins/postcss-trigonometric-functions/docs/README.md @@ -2,6 +2,7 @@ + From d36fd1b2ddde988dd4b8c96f3e50e5277c89f908 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:08:40 +0200 Subject: [PATCH 06/11] docs --- plugins/css-prefers-color-scheme/README.md | 2 +- plugins/css-prefers-color-scheme/docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/css-prefers-color-scheme/README.md b/plugins/css-prefers-color-scheme/README.md index 88158f137..91b076bf1 100644 --- a/plugins/css-prefers-color-scheme/README.md +++ b/plugins/css-prefers-color-scheme/README.md @@ -201,7 +201,7 @@ The `removeListener` function removes the native `prefers-color-scheme` listener, which may or may not be applied, depending on your browser support. This is provided to give you complete control over plugin cleanup. -#### debug mode +#### debug If styles are not applied you can enable debug mode to log exceptions. diff --git a/plugins/css-prefers-color-scheme/docs/README.md b/plugins/css-prefers-color-scheme/docs/README.md index bbe26804e..15eadd1f7 100644 --- a/plugins/css-prefers-color-scheme/docs/README.md +++ b/plugins/css-prefers-color-scheme/docs/README.md @@ -132,7 +132,7 @@ The `removeListener` function removes the native `prefers-color-scheme` listener, which may or may not be applied, depending on your browser support. This is provided to give you complete control over plugin cleanup. -#### debug mode +#### debug If styles are not applied you can enable debug mode to log exceptions. From 7725499ac29cbd8348bc56764e2cab666c6d8ef9 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:32:07 +0200 Subject: [PATCH 07/11] changelog --- plugins/css-has-pseudo/CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/plugins/css-has-pseudo/CHANGELOG.md b/plugins/css-has-pseudo/CHANGELOG.md index 5cbec5ed4..141631b3b 100644 --- a/plugins/css-has-pseudo/CHANGELOG.md +++ b/plugins/css-has-pseudo/CHANGELOG.md @@ -1,5 +1,32 @@ # Changes to CSS Has Pseudo +### Unreleased (major) + +[Read the full changelog](https://github.com/csstools/postcss-plugins/wiki/PostCSS-Preset-Env-8) + +- Breaking: removed old CDN urls +- Added: 'hover' options for browser polyfill +- Added: 'observedAttributes' options for browser polyfill +- Added: 'forcePolyfill' options for browser polyfill +- Added: Rules within `@supports selector(:has(something))` won't be transformed. +- Fix: Use base36 encoding to support all possible selectors. + +#### How to migrate : + +##### Re-build your CSS with the new version of the library. + +##### If you use a CDN url, please update it. + +```diff +- ++ +``` + +```diff +- ++ +``` + ### 3.0.4 (February 5, 2022) - Rebuild of browser polyfills From 8ed481a3c7cf60618ef5ed3b6103b7442d845e4b Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:35:05 +0200 Subject: [PATCH 08/11] fix --- plugins/css-has-pseudo/.tape.mjs | 4 ++-- plugins/css-has-pseudo/README.md | 7 ++----- plugins/css-has-pseudo/docs/README.md | 6 +++--- ...e-true.expect.css => example.preserve-false.expect.css} | 3 --- 4 files changed, 7 insertions(+), 13 deletions(-) rename plugins/css-has-pseudo/test/examples/{example.preserve-true.expect.css => example.preserve-false.expect.css} (70%) diff --git a/plugins/css-has-pseudo/.tape.mjs b/plugins/css-has-pseudo/.tape.mjs index ca4fab403..58629ecdf 100644 --- a/plugins/css-has-pseudo/.tape.mjs +++ b/plugins/css-has-pseudo/.tape.mjs @@ -23,10 +23,10 @@ postcssTape(plugin)({ 'examples/example': { message: 'minimal example', }, - 'examples/example:preserve-true': { + 'examples/example:preserve-false': { message: 'minimal example', options: { - preserve: true + preserve: false } }, 'generated-selector-cases': { diff --git a/plugins/css-has-pseudo/README.md b/plugins/css-has-pseudo/README.md index 3700459fd..d89fb5629 100644 --- a/plugins/css-has-pseudo/README.md +++ b/plugins/css-has-pseudo/README.md @@ -49,10 +49,10 @@ instructions for: ### preserve The `preserve` option determines whether the original notation -is preserved. By default, it is not preserved. +is preserved. By default the original rules are preserved. ```js -postcssHasPseudo({ preserve: true }) +postcssHasPseudo({ preserve: false }) ``` ```pcss @@ -65,9 +65,6 @@ postcssHasPseudo({ preserve: true }) [csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { margin-bottom: 1.5rem; } -.title:has(+ p) { - margin-bottom: 1.5rem; -} ``` ### specificityMatchingName diff --git a/plugins/css-has-pseudo/docs/README.md b/plugins/css-has-pseudo/docs/README.md index 439462e47..5b1b71825 100644 --- a/plugins/css-has-pseudo/docs/README.md +++ b/plugins/css-has-pseudo/docs/README.md @@ -35,10 +35,10 @@ ### preserve The `preserve` option determines whether the original notation -is preserved. By default, it is not preserved. +is preserved. By default the original rules are preserved. ```js -({ preserve: true }) +({ preserve: false }) ``` ```pcss @@ -46,7 +46,7 @@ is preserved. By default, it is not preserved. /* becomes */ - + ``` ### specificityMatchingName diff --git a/plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css b/plugins/css-has-pseudo/test/examples/example.preserve-false.expect.css similarity index 70% rename from plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css rename to plugins/css-has-pseudo/test/examples/example.preserve-false.expect.css index 451df21af..330372f0c 100644 --- a/plugins/css-has-pseudo/test/examples/example.preserve-true.expect.css +++ b/plugins/css-has-pseudo/test/examples/example.preserve-false.expect.css @@ -1,6 +1,3 @@ [csstools-has-1a-38-2x-38-30-2t-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist) { margin-bottom: 1.5rem; } -.title:has(+ p) { - margin-bottom: 1.5rem; -} From 82ce9249b7efe7a7a4e5d33fbd3b8f916326078a Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:36:32 +0200 Subject: [PATCH 09/11] fix --- plugins/css-has-pseudo/README.md | 2 +- plugins/css-has-pseudo/docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/css-has-pseudo/README.md b/plugins/css-has-pseudo/README.md index d89fb5629..90efbaa04 100644 --- a/plugins/css-has-pseudo/README.md +++ b/plugins/css-has-pseudo/README.md @@ -103,7 +103,7 @@ After : ### Specificity -`:has` transforms will result in at least one tag selector with specificity `0, 1, 0`. +`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`. If your selector has only tags we won't be able to match the original specificity. Before : diff --git a/plugins/css-has-pseudo/docs/README.md b/plugins/css-has-pseudo/docs/README.md index 5b1b71825..cb34dd928 100644 --- a/plugins/css-has-pseudo/docs/README.md +++ b/plugins/css-has-pseudo/docs/README.md @@ -85,7 +85,7 @@ After : ### Specificity -`:has` transforms will result in at least one tag selector with specificity `0, 1, 0`. +`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`. If your selector has only tags we won't be able to match the original specificity. Before : From 62c16434a31a1be1c9daf7c9e9dbc3d5733db856 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:36:54 +0200 Subject: [PATCH 10/11] fix --- plugins/css-has-pseudo/docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/css-has-pseudo/docs/README.md b/plugins/css-has-pseudo/docs/README.md index cb34dd928..66592a64d 100644 --- a/plugins/css-has-pseudo/docs/README.md +++ b/plugins/css-has-pseudo/docs/README.md @@ -85,8 +85,8 @@ After : ### Specificity -`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`. -If your selector has only tags we won't be able to match the original specificity. +`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`.
+If your selector only has tags we won't be able to match the original specificity. Before : From 0c1d8d83ec24510970fcbdc3a034d0797932ac4a Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 25 Jun 2022 10:37:19 +0200 Subject: [PATCH 11/11] fix --- plugins/css-has-pseudo/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/css-has-pseudo/README.md b/plugins/css-has-pseudo/README.md index 90efbaa04..d19fe1266 100644 --- a/plugins/css-has-pseudo/README.md +++ b/plugins/css-has-pseudo/README.md @@ -103,8 +103,8 @@ After : ### Specificity -`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`. -If your selector has only tags we won't be able to match the original specificity. +`:has` transforms will result in at least one attribute selector with specificity `0, 1, 0`.
+If your selector only has tags we won't be able to match the original specificity. Before :