From b9112b7fbd9ec131deefece3ba832905e374f3f5 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sat, 25 Jun 2022 15:09:53 +0200
Subject: [PATCH 1/2] css-has-pseudo : make the experimental plugin a noop
---
experimental/css-has-pseudo/.tape.mjs | 63 +-
experimental/css-has-pseudo/CHANGELOG.md | 7 +
.../css-has-pseudo/INSTALL-POSTCSS.md | 165 ---
experimental/css-has-pseudo/README-BROWSER.md | 152 ---
experimental/css-has-pseudo/README-POSTCSS.md | 172 ---
experimental/css-has-pseudo/README.md | 107 +-
experimental/css-has-pseudo/package.json | 9 -
experimental/css-has-pseudo/src/browser.js | 263 +---
.../css-has-pseudo/src/encode/decode.mjs | 17 -
.../css-has-pseudo/src/encode/encode.mjs | 20 -
.../css-has-pseudo/src/encode/extract.mjs | 91 --
.../css-has-pseudo/src/encode/test.mjs | 155 ---
experimental/css-has-pseudo/src/index.ts | 112 +-
.../css-has-pseudo/test/_browser.html | 1189 -----------------
experimental/css-has-pseudo/test/_browser.mjs | 83 --
.../css-has-pseudo/test/basic.expect.css | 131 --
16 files changed, 17 insertions(+), 2719 deletions(-)
delete mode 100644 experimental/css-has-pseudo/INSTALL-POSTCSS.md
delete mode 100644 experimental/css-has-pseudo/README-BROWSER.md
delete mode 100644 experimental/css-has-pseudo/README-POSTCSS.md
delete mode 100644 experimental/css-has-pseudo/src/encode/decode.mjs
delete mode 100644 experimental/css-has-pseudo/src/encode/encode.mjs
delete mode 100644 experimental/css-has-pseudo/src/encode/extract.mjs
delete mode 100644 experimental/css-has-pseudo/src/encode/test.mjs
delete mode 100644 experimental/css-has-pseudo/test/_browser.html
delete mode 100644 experimental/css-has-pseudo/test/_browser.mjs
diff --git a/experimental/css-has-pseudo/.tape.mjs b/experimental/css-has-pseudo/.tape.mjs
index ba1c9f37e..f3b59acde 100644
--- a/experimental/css-has-pseudo/.tape.mjs
+++ b/experimental/css-has-pseudo/.tape.mjs
@@ -1,68 +1,9 @@
import postcssTape from '../../packages/postcss-tape/dist/index.mjs';
import plugin from '@csstools/css-has-pseudo-experimental';
-import postcssLogical from 'postcss-logical';
-import postcssNesting from 'postcss-nesting';
-import postcssDirPseudoClass from 'postcss-dir-pseudo-class';
postcssTape(plugin)({
'basic': {
- message: 'supports basic usage'
+ message: 'supports basic usage',
+ warnings: 1
},
- 'basic:preserve': {
- message: 'supports { preserve: false } usage',
- options: {
- preserve: false
- }
- },
- 'basic:specificity-matching-name': {
- message: 'supports { specificityMatchingName: "other-thing-that-does-not-exist" } usage',
- options: {
- specificityMatchingName: 'other-thing-that-does-not-exist'
- }
- },
- 'generated-selector-cases': {
- message: 'correctly handles generated cases',
- warnings: 1,
- options: {
- 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/experimental/css-has-pseudo/CHANGELOG.md b/experimental/css-has-pseudo/CHANGELOG.md
index 5ad138f67..88f5f19fd 100644
--- a/experimental/css-has-pseudo/CHANGELOG.md
+++ b/experimental/css-has-pseudo/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changes to CSS Has Pseudo
+### Unreleased
+
+- `@csstools/css-has-pseudo-experimental` is no longer supported. Please use `css-has-pseudo` instead.
+All issues have been resolved in the main plugin and the experimental plugin is no longer maintained.
+
+⚠️ This experimental plugin no longer has any effect on the output of your CSS.
+
### 0.5.2 (June 4, 2022)
- Update `@csstools/selector-specificity` (major)
diff --git a/experimental/css-has-pseudo/INSTALL-POSTCSS.md b/experimental/css-has-pseudo/INSTALL-POSTCSS.md
deleted file mode 100644
index f1f0052ce..000000000
--- a/experimental/css-has-pseudo/INSTALL-POSTCSS.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# Installing PostCSS
-
-[EXPERIMENTAL CSS Has Pseudo] runs in all Node environments, with special instructions for:
-
-⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo)
-
-| [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:
-
-```bash
-npm install css-has-pseudo --save-dev
-```
-
-Use it as a [PostCSS] plugin:
-
-```js
-const postcss = require('postcss');
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-postcss([
- cssHasPseudoExperimental(/* pluginOptions */)
-]).process(YOUR_CSS /*, processOptions */);
-```
-
-## PostCSS CLI
-
-Add [PostCSS CLI] to your project:
-
-```bash
-npm install postcss-cli --save-dev
-```
-
-Use [CSS Has Pseudo] in your `postcss.config.js` configuration file:
-
-```js
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-module.exports = {
- plugins: [
- cssHasPseudoExperimental(/* pluginOptions */)
- ]
-}
-```
-
-## Webpack
-
-Add [PostCSS Loader] to your project:
-
-```bash
-npm install postcss-loader --save-dev
-```
-
-Use [CSS Has Pseudo] in your Webpack configuration:
-
-```js
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-module.exports = {
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [
- 'style-loader',
- { loader: 'css-loader', options: { importLoaders: 1 } },
- { loader: 'postcss-loader', options: {
- ident: 'postcss',
- plugins: () => [
- cssHasPseudoExperimental(/* pluginOptions */)
- ]
- } }
- ]
- }
- ]
- }
-}
-```
-
-## Create React App
-
-Add [React App Rewired] and [React App Rewire PostCSS] to your project:
-
-```bash
-npm install react-app-rewired react-app-rewire-postcss --save-dev
-```
-
-Use [React App Rewire PostCSS] and [CSS Has Pseudo] in your
-`config-overrides.js`
-file:
-
-```js
-const reactAppRewirePostcss = require('react-app-rewire-postcss');
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-module.exports = config => reactAppRewirePostcss(config, {
- plugins: () => [
- cssHasPseudoExperimental(/* pluginOptions */)
- ]
-});
-```
-
-## Gulp
-
-Add [Gulp PostCSS] to your project:
-
-```bash
-npm install gulp-postcss --save-dev
-```
-
-Use [CSS Has Pseudo] in your Gulpfile:
-
-```js
-const postcss = require('gulp-postcss');
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-gulp.task('css', () => gulp.src('./src/*.css').pipe(
- postcss([
- cssHasPseudoExperimental(/* pluginOptions */)
- ])
-).pipe(
- gulp.dest('.')
-));
-```
-
-## Grunt
-
-Add [Grunt PostCSS] to your project:
-
-```bash
-npm install grunt-postcss --save-dev
-```
-
-Use [CSS Has Pseudo] in your Gruntfile:
-
-```js
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-grunt.loadNpmTasks('grunt-postcss');
-
-grunt.initConfig({
- postcss: {
- options: {
- use: [
- cssHasPseudoExperimental(/* pluginOptions */)
- ]
- },
- dist: {
- src: '*.css'
- }
- }
-});
-```
-
-[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-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 CLI]: https://github.com/postcss/postcss-cli
-[PostCSS Loader]: https://github.com/postcss/postcss-loader
-[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/experimental/css-has-pseudo/README-BROWSER.md b/experimental/css-has-pseudo/README-BROWSER.md
deleted file mode 100644
index ad2664c9d..000000000
--- a/experimental/css-has-pseudo/README-BROWSER.md
+++ /dev/null
@@ -1,152 +0,0 @@
-# EXPERIMENTAL : CSS Has Pseudo for Browsers [
][EXPERIMENTAL CSS Has Pseudo]
-
-[![NPM Version][npm-img]][npm-url]
-[
][discord]
-
-⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo)
-
-[EXPERIMENTAL CSS Has Pseudo] lets you style elements relative to other elements in CSS,
-following the [Selectors Level 4] specification.
-
-## Usage
-
-Add [EXPERIMENTAL CSS Has Pseudo] to your build tool:
-
-```bash
-npm install @csstools/css-has-pseudo-experimental
-```
-
-Then include and initialize it on your document:
-
-```js
-const cssHasPseudo = require('@csstools/css-has-pseudo-experimental/browser');
-
-cssHasPseudo(document);
-```
-
-```html
-
-
-
-```
-
-⚠️ Please use a versioned url, like this : `https://unpkg.com/@csstools/css-has-pseudo-experimental@0.2.0/dist/browser-global.js`
-Without the version, you might unexpectedly get a new major version of the library with breaking changes.
-
-## 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.
-
-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 :**
-
-```html
-
-
-```
-
-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 HTTP header `Access-Control-Allow-Origin: ` to your CSS file.
-- add `crossorigin="anonymous"` to the `` tage 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
-
-```
-
-## Options
-
-### 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 });
-```
-
-### 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 want to 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 });
-```
-
-## 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`
-
-[discord]: https://discord.gg/bUadyRwkJS
-[npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg
-[npm-url]: https://www.npmjs.com/package/@csstools/css-has-pseudo-experimental
-
-[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo
-[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has
diff --git a/experimental/css-has-pseudo/README-POSTCSS.md b/experimental/css-has-pseudo/README-POSTCSS.md
deleted file mode 100644
index 4e2b87bcd..000000000
--- a/experimental/css-has-pseudo/README-POSTCSS.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# EXPERIMENTAL : CSS Has Pseudo for PostCSS [
][EXPERIMENTAL CSS Has Pseudo]
-
-[![NPM Version][npm-img]][npm-url]
-[
][discord]
-
-⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo)
-
-[EXPERIMENTAL 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 */
-
-[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) {
- background-color: yellow;
-}
-
-body:has(:focus) {
- background-color: yellow;
-}
-```
-
-[EXPERIMENTAL 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 [EXPERIMENTAL CSS Has Pseudo] to your project:
-
-```bash
-npm install @csstools/css-has-pseudo-experimental --save-dev
-```
-
-Use [EXPERIMENTAL CSS Has Pseudo] as a [PostCSS] plugin:
-
-```js
-const postcss = require('postcss');
-const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental');
-
-postcss([
- cssHasPseudoExperimental(/* pluginOptions */)
-]).process(YOUR_CSS /*, processOptions */);
-```
-
-[EXPERIMENTAL CSS Has Pseudo] runs in all Node environments, with special
-instructions for:
-
-| [Node](INSTALL-POSTCSS.md#node) | [PostCSS CLI](INSTALL-POSTCSS.md#postcss-cli) | [Webpack](INSTALL-POSTCSS.md#webpack) | [Create React App](INSTALL-POSTCSS.md#create-react-app) | [Gulp](INSTALL-POSTCSS.md#gulp) | [Grunt](INSTALL-POSTCSS.md#grunt) |
-| --- | --- | --- | --- | --- | --- |
-
-## Options
-
-### preserve
-
-The `preserve` option defines whether the original selector should remain. By
-default, the original selector is preserved.
-
-```js
-cssHasPseudoExperimental({ preserve: false });
-```
-
-```css
-body:has(:focus) {
- background-color: yellow;
-}
-
-/* becomes */
-
-[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) {
- background-color: yellow;
-}
-```
-
-### 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
-postcss([
- cssHasPseudoExperimental({ specificityMatchingName: 'something-random' })
-]).process(YOUR_CSS /*, processOptions */);
-```
-
-[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.
-
-For `postcss-preset-env` we take care to handle this for you.
-
-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.
-
-## PostCSS Preset Env
-
-When you use `postcss-preset-env` you must disable the regular plugin.
-
-The experimental plugin must be added after any other plugin that modifies selectors.
-
-```js
-plugins: [
- // other plugins
- postcssPresetEnv({
- features: {
- 'css-has-pseudo': false
- }
- }),
- // other plugins
- cssHasPseudoExperimental(), // last
-]
-```
-
-[discord]: https://discord.gg/bUadyRwkJS
-[npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg
-[npm-url]: https://www.npmjs.com/package/@csstools/css-has-pseudo-experimental
-
-[PostCSS]: https://github.com/postcss/postcss
-[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo
-[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has-pseudo
diff --git a/experimental/css-has-pseudo/README.md b/experimental/css-has-pseudo/README.md
index e267b6cb1..d32523ec7 100644
--- a/experimental/css-has-pseudo/README.md
+++ b/experimental/css-has-pseudo/README.md
@@ -3,111 +3,10 @@
[![NPM Version][npm-img]][npm-url]
[
][discord]
-⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo)
+`@csstools/css-has-pseudo-experimental` is no longer supported. Please use `css-has-pseudo` instead.
+All issues have been resolved in the main plugin and the experimental plugin is no longer maintained.
-[EXPERIMENTAL 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 */
-}
-
-h1:has(+ p) {
- /* style level 1 headings that are followed by a paragraph */
-}
-
-section:not(:has(h1, h2, h3, h4, h5, h6)) {
- /* style sections that don’t contain any heading elements */
-}
-
-body:has(:focus) {
- /* style the body if it contains a focused element */
-}
-```
-
-Next, use your transformed CSS with this script:
-
-```html
-
-
-
-```
-
-⚠️ Please use a versioned url, like this : `https://unpkg.com/@csstools/css-has-pseudo-experimental@0.1.0/dist/browser-global.js`
-Without the version, you might unexpectedly get a new major version of the library with breaking changes.
-
-⚠️ If you were using an older version via a CDN, please update the entire url.
-The old URL will no longer work.
-
-That’s it. The script works in most browser versions, including
-Internet Explorer 11. With a [Mutation Observer polyfill], the script will work
-down to Internet Explorer 9.
-
-See [README BROWSER](README-BROWSER.md) for more information.
-
-## How it works
-
-The [PostCSS plugin](README-POSTCSS.md) clones rules containing `:has`,
-replacing them with an alternative `[:has]` selector.
-
-```css
-body:has(:focus) {
- background-color: yellow;
-}
-
-section:not(:has(h1, h2, h3, h4, h5, h6)) {
- background-color: gray;
-}
-
-/* becomes */
-
-[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) {
- background-color: yellow;
-}
-
-body:has(:focus) {
- background-color: yellow;
-}
-
-[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) {
- background-color: gray;
-}
-
-section:not(:has(h1, h2, h3, h4, h5, h6)) {
- background-color: gray;
-}
-```
-
-Next, the [JavaScript library](README-BROWSER.md) adds a `[:has]` attribute to
-elements otherwise matching `:has` natively.
-
-```html
-
-
-
-```
-
-## PostCSS Preset Env
-
-When you use `postcss-preset-env` you must disable the regular plugin.
-
-The experimental plugin must be added after any other plugin that modifies selectors.
-
-```js
-plugins: [
- // other plugins
- postcssPresetEnv({
- features: {
- 'css-has-pseudo': false
- }
- }),
- // other plugins
- cssHasPseudoExperimental(), // last
-]
-```
+⚠️ This experimental plugin no longer has any effect on the output of your CSS.
[discord]: https://discord.gg/bUadyRwkJS
[npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg
diff --git a/experimental/css-has-pseudo/package.json b/experimental/css-has-pseudo/package.json
index cbd705593..3a8bbd880 100644
--- a/experimental/css-has-pseudo/package.json
+++ b/experimental/css-has-pseudo/package.json
@@ -35,17 +35,9 @@
"README.md",
"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",
"clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"",
@@ -55,7 +47,6 @@
"prepublishOnly": "npm run clean && npm run build && npm run test",
"stryker": "stryker run --logLevel error",
"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:unit": "node ./src/encode/test.mjs"
diff --git a/experimental/css-has-pseudo/src/browser.js b/experimental/css-has-pseudo/src/browser.js
index 398156d8c..14a87b9a8 100644
--- a/experimental/css-has-pseudo/src/browser.js
+++ b/experimental/css-has-pseudo/src/browser.js
@@ -1,262 +1,3 @@
-/* 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
- const attributeElement = document.createElement('x');
-
- // walk all stylesheets to collect observed css rules
- [].forEach.call(document.styleSheets, walkStyleSheet);
- transformObservedItemsThrottled();
-
- // observe DOM modifications that affect selectors
- 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);
- }
- }
- }
-
- let transformObservedItemsThrottledBusy = false;
- function transformObservedItemsThrottled() {
- if (transformObservedItemsThrottledBusy) {
- cancelAnimationFrame(transformObservedItemsThrottledBusy);
- }
-
- transformObservedItemsThrottledBusy = requestAnimationFrame(() => {
- transformObservedItems();
- });
- }
-
- // transform observed css rules
- function transformObservedItems() {
- observedItems.forEach((item) => {
- const nodes = [];
-
- let matches = [];
- try {
- matches = document.querySelectorAll(item.selector);
- } catch (e) {
- if (options.debug) {
- console.error(e);
- }
- return;
- }
-
- [].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;
- });
- }
-
- // remove any observed cssrules that no longer apply
- function cleanupObservedCssRules() {
- [].push.apply(
- observedItems,
- observedItems.splice(0).filter((item) => {
- return item.rule.parentStyleSheet &&
- item.rule.parentStyleSheet.ownerNode &&
- document.documentElement.contains(item.rule.parentStyleSheet.ownerNode);
- }),
- );
- }
-
- // walk a stylesheet to collect observed css rules
- function walkStyleSheet(styleSheet) {
- try {
- // walk a css rule to collect observed css rules
- [].forEach.call(styleSheet.cssRules || [], (rule) => {
- if (rule.selectorText) {
- 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 (e) {
- if (options.debug) {
- console.error(e);
- }
- }
- }
+export default function cssHasPseudo() {
+ console.log('"@csstools/css-has-pseudo-experimental" is no longer supported. Please use "css-has-pseudo" instead.');
}
diff --git a/experimental/css-has-pseudo/src/encode/decode.mjs b/experimental/css-has-pseudo/src/encode/decode.mjs
deleted file mode 100644
index a24302530..000000000
--- a/experimental/css-has-pseudo/src/encode/decode.mjs
+++ /dev/null
@@ -1,17 +0,0 @@
-
-/** 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/experimental/css-has-pseudo/src/encode/encode.mjs b/experimental/css-has-pseudo/src/encode/encode.mjs
deleted file mode 100644
index 36d0a4ae8..000000000
--- a/experimental/css-has-pseudo/src/encode/encode.mjs
+++ /dev/null
@@ -1,20 +0,0 @@
-
-/** 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/experimental/css-has-pseudo/src/encode/extract.mjs b/experimental/css-has-pseudo/src/encode/extract.mjs
deleted file mode 100644
index 366ab150c..000000000
--- a/experimental/css-has-pseudo/src/encode/extract.mjs
+++ /dev/null
@@ -1,91 +0,0 @@
-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/experimental/css-has-pseudo/src/encode/test.mjs b/experimental/css-has-pseudo/src/encode/test.mjs
deleted file mode 100644
index 7ff593799..000000000
--- a/experimental/css-has-pseudo/src/encode/test.mjs
+++ /dev/null
@@ -1,155 +0,0 @@
-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/experimental/css-has-pseudo/src/index.ts b/experimental/css-has-pseudo/src/index.ts
index cabc31012..627c63d34 100644
--- a/experimental/css-has-pseudo/src/index.ts
+++ b/experimental/css-has-pseudo/src/index.ts
@@ -1,100 +1,10 @@
-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 + ')';
-
+const creator: PluginCreator = () => {
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;
- }
+ Once(root, { result }) {
+ root.warn(result, '"@csstools/css-has-pseudo-experimental" is no longer supported. Please use "css-has-pseudo" instead.');
},
};
};
@@ -102,19 +12,3 @@ const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: str
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/experimental/css-has-pseudo/test/_browser.html b/experimental/css-has-pseudo/test/_browser.html
deleted file mode 100644
index 4d8c007f9..000000000
--- a/experimental/css-has-pseudo/test/_browser.html
+++ /dev/null
@@ -1,1189 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/experimental/css-has-pseudo/test/_browser.mjs b/experimental/css-has-pseudo/test/_browser.mjs
deleted file mode 100644
index a8220d97c..000000000
--- a/experimental/css-has-pseudo/test/_browser.mjs
+++ /dev/null
@@ -1,83 +0,0 @@
-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/experimental/css-has-pseudo/test/basic.expect.css b/experimental/css-has-pseudo/test/basic.expect.css
index 052c39c82..e37868806 100644
--- a/experimental/css-has-pseudo/test/basic.expect.css
+++ b/experimental/css-has-pseudo/test/basic.expect.css
@@ -1,55 +1,27 @@
-[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(does-not-exist):not(does-not-exist) {
- order: 2;
-}
-
a:has(> img) {
order: 2;
}
-[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) {
- order: 3;
-}
-
h1:has(+ p) {
order: 3;
}
-[csstools-has-2w-1d-1m-2w-2p-37-14-3i-w-34-15]:not(does-not-exist):not(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(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: 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;
-}
-
body:not(:has(:focus)) {
order: 7;
}
@@ -59,202 +31,102 @@ body:not(: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(.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;
}
@@ -266,9 +138,6 @@ body:not(:has(:focus)) {
}
@supports (display: grid) {
- [csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] {
- order: 33;
- }
:has(:focus) {
order: 33;
}
From 70852d6747cedd2658bd5ff176576ef9817fef49 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sat, 25 Jun 2022 15:19:26 +0200
Subject: [PATCH 2/2] fix tests
---
experimental/css-has-pseudo/package.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/experimental/css-has-pseudo/package.json b/experimental/css-has-pseudo/package.json
index 3a8bbd880..c8fdb2890 100644
--- a/experimental/css-has-pseudo/package.json
+++ b/experimental/css-has-pseudo/package.json
@@ -46,10 +46,9 @@
"lint:package-json": "node ../../.github/bin/format-package-json.mjs",
"prepublishOnly": "npm run clean && npm run build && npm run test",
"stryker": "stryker run --logLevel error",
- "test": "node .tape.mjs && npm run test:unit && npm run test:exports",
+ "test": "node .tape.mjs && npm run test:exports",
"test:exports": "node ./test/_import.mjs && node ./test/_require.cjs",
- "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs",
- "test:unit": "node ./src/encode/test.mjs"
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
},
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo#readme",
"repository": {