postcss-nested-ancestors
Version:
PostCSS plugin to reference any ancestor selector in nested CSS
260 lines (196 loc) • 6.9 kB
Markdown
in nested CSS.
When writing modular nested CSS, `&` current parent selector is often not enough.
**PostCSS Nested ancestors** introduces `^&` selector which let you reference **any parent ancestor selector** with an easy and customizable interface.
This plugin should be used **before** a PostCSS rules unwrapper like [postcss-nested].
See [PostCSS] docs for examples for your environment.
```
.level-1 {
| | .level-2 {
| | | .level-3 {
| | | | .level-4 {
| | | | |
| | | | --- & {} /* & = ".level-1 .level-2 .level-3 .level-4" */
| | | ------- ^& {} /* ^& = ".level-1 .level-2 .level-3" */
| | ----------- ^^& {} /* ^^& = ".level-1 .level-2" */
| --------------- ^^^& {} /* ^^^& = ".level-1" */
------------------- ^^^^& {} /* ^^^^& = "" */
}
}
}
}
```
```css
/* Without postcss-nested-ancestors */
.MyComponent
&-part{}
&:hover {
> .MyComponent-part {} /* Must manually repeat ".MyComponent" for each child */
}
}
/* With postcss-nested-ancestors */
.MyComponent
&-part{}
&:hover {
> ^&-part {} /* Skip ":hover" inheritance here */
}
}
/* After postcss-nested-ancestors */
.MyComponent {
&-part{}
&:hover {
> .MyComponent-part {}
}
/* After postcss-nested */
.MyComponent {}
.MyComponent-part {}
.MyComponent:hover {}
.MyComponent:hover > .MyComponent-part {} /* No ":hover" inheritance here! */
```
Currently another plugin - [postcss-current-selector] - has tried to solve the problem of referencing ancestors selector. It works great, but its approach involves assigning ancestor selectors to special variables to be later processed by a further postcss plugin [postcss-simple-vars].
**PostCSS Nested ancestors** instead replaces special ancestor selectors, makes no use of variable assignment and produces an output ready to be unwrapped with [postcss-nested].
```console
$ npm install --save-dev postcss postcss-nested-ancestors
```
```js
postcss([require('postcss-nested-ancestors')]);
```
Type: `string`
Default: `^&`
Ancestor selector pattern (utility option to automatically set both `levelSymbol` and `parentSymbol`)
### levelSymbol
Type: `string`
Default: `^`
Define ancestor selector fragment relative to the matching nesting level
### parentSymbol
Type: `string`
Default: `&`
Ancestor selector base symbol
### replaceDeclarations (experimental)
Type: `boolean`
Default: `false`
If this is true then this plugin will look through your declaration values for the placeholder symbol and replace them with specified selector.
An use case for this if enabling [postcss-ref](https://github.com/morishitter/postcss-ref) to work with dynamic `@ref` selectors. Read discussion [here](https://github.com/toomuchdesign/postcss-nested-ancestors/pull/3).
```css
/* Before */
.foo {
&:last-child {
border-top: ref(^&, border-bottom);
}
}
/* After PostCSS Nested ancestors and PostCSS Nested */
.foo {
}
.foo:last-child {
border-top: ref(.foo, border-bottom);
}
```
This plugin currently fails when trying to replace **more than one different ancestor placeholder in a single rule selector**. This scenario has not been considered in order to not bloat the code with a remote use case.
More precisely, all ancestor placeholders are replaced, but processed as if they where the equal to the first ancestor placeholder found in selector.
In general, **do not use more than one ancestor placeholder in a single rule selector**. Anyway, this use case can be rewritten by **splitting the selectors in multiple nested rules** (see edge case 2).
```css
/* 2 equal ancestor placeholders in single rule selector */
.a {
&:hover {
^&^&-b {
}
}
}
/* Output: It works but casts a warning */
.a {
&:hover {
.a.a-b {
}
}
}
```
```css
/* 2 different ancestor placeholders in single rule selector */
.a {
&-b {
&:hover {
/* Will be processed as ^&^&-c{}, sorry! */
^&^^&-c {
}
}
}
}
/* Wrong output: All placeholder replaced with the value of the first one */
.a {
&-b {
&:hover {
/* Expected output: .a-b.a-c{}*/
.a-b.a-b-c {
}
}
}
}
/* This use case can be rewritten as: */
.a {
&-b {
&:hover {
^& {
&^^^&-c {
}
}
}
}
}
```
`replaceDeclarations` options used in a complex nesting scenario might have undesired outputs because of the different nature of CSS selectors and and declaration values.
In general, avoid replacing declaration values when inside a rule with multiple selectors (but why should you?). In other words don't get yourself into trouble!
Here is an example of what you don't want to do.
```css
/* Don't replace declaration value inside multiple selector rules */
.a1,
.a2 {
&:hover {
&:before {
content: '^^&';
}
}
}
/* Output */
.a1,
.a2 {
&:hover {
&:before {
content: '.a1,.a2';
}
}
}
```
Contributions are super welcome, but please follow the conventions below if you want to do a pull request:
- Create a new branch and make the pull request from that branch
- Each pull request for a single feature or bug fix
- If you are planning on doing something big, please discuss first with [@toomuchdesign](http://www.twitter.com/toomuchdesign) about it
- Update tests (`test.js`) covering new features
## Todo's
- Better comment source code
[postcss]: https://github.com/postcss/postcss
[ci-badge]: https://github.com/toomuchdesign/postcss-nested-ancestors/actions/workflows/ci.yml/badge.svg
[ci]: https://github.com/toomuchdesign/postcss-nested-ancestors/actions/workflows/ci.yml
[coveralls-badge]: https://coveralls.io/repos/github/toomuchdesign/postcss-nested-ancestors/badge.svg?branch=master
[coveralls]: https://coveralls.io/github/toomuchdesign/postcss-nested-ancestors?branch=master
[npm]: https://www.npmjs.com/package/postcss-nested-ancestors
[npm-version-badge]: https://img.shields.io/npm/v/postcss-nested-ancestors.svg
[postcss-current-selector]: https://github.com/komlev/postcss-current-selector
[postcss-nested]: https://github.com/postcss/postcss-nested
[postcss-simple-vars]: https://github.com/postcss/postcss-simple-vars
[![Build status][ci-badge]][ci]
[![Npm version][npm-version-badge]][npm]
[![Test coverage report][coveralls-badge]][coveralls]
[ ] plugin to reference any parent ancestor selector