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]
-
-[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]
-
-[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]
-[![NPM Version][npm-img]][npm-url]
-[ ][discord]
+[ ][npm-url] [ ][css-url] [ ][cli-url] [ ][discord]
-[CSS Has Pseudo] lets you style elements relative to other elements in CSS,
-following the [Selectors Level 4] specification.
-
-[](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-url] [ ][css-url] [ ][cli-url] [ ][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 :