summaryrefslogtreecommitdiff
path: root/node_modules/postcss-nested
diff options
context:
space:
mode:
authorPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:54:57 +0100
committerPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:57:48 +0100
commitb1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch)
tree49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/postcss-nested
Docs
Diffstat (limited to 'node_modules/postcss-nested')
-rw-r--r--node_modules/postcss-nested/LICENSE20
-rw-r--r--node_modules/postcss-nested/README.md86
-rw-r--r--node_modules/postcss-nested/index.d.ts41
-rw-r--r--node_modules/postcss-nested/index.js361
-rw-r--r--node_modules/postcss-nested/package.json28
5 files changed, 536 insertions, 0 deletions
diff --git a/node_modules/postcss-nested/LICENSE b/node_modules/postcss-nested/LICENSE
new file mode 100644
index 0000000..1ae47a2
--- /dev/null
+++ b/node_modules/postcss-nested/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright 2014 Andrey Sitnik <andrey@sitnik.ru>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/postcss-nested/README.md b/node_modules/postcss-nested/README.md
new file mode 100644
index 0000000..7e78ec8
--- /dev/null
+++ b/node_modules/postcss-nested/README.md
@@ -0,0 +1,86 @@
+# PostCSS Nested
+
+<img align="right" width="135" height="95"
+ title="Philosopher’s stone, logo of PostCSS"
+ src="https://postcss.org/logo-leftp.svg">
+
+[PostCSS] plugin to unwrap nested rules closer to Sass syntax.
+
+```css
+.phone {
+ &_title {
+ width: 500px;
+ @media (max-width: 500px) {
+ width: auto;
+ }
+ body.is_dark & {
+ color: white;
+ }
+ }
+ img {
+ display: block;
+ }
+}
+
+.title {
+ font-size: var(--font);
+
+ @at-root html {
+ --font: 16px
+ }
+}
+```
+
+will be processed to:
+
+```css
+.phone_title {
+ width: 500px;
+}
+@media (max-width: 500px) {
+ .phone_title {
+ width: auto;
+ }
+}
+body.is_dark .phone_title {
+ color: white;
+}
+.phone img {
+ display: block;
+}
+
+.title {
+ font-size: var(--font);
+}
+html {
+ --font: 16px
+}
+```
+
+Related plugins:
+
+* Use [`postcss-current-selector`] **after** this plugin if you want
+ to use current selector in properties or variables values.
+* Use [`postcss-nested-ancestors`] **before** this plugin if you want
+ to reference any ancestor element directly in your selectors with `^&`.
+
+Alternatives:
+
+* See also [`postcss-nesting`], which implements [CSSWG draft].
+* [`postcss-nested-props`] for nested properties like `font-size`.
+
+<a href="https://evilmartians.com/?utm_source=postcss-nested">
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"
+ alt="Sponsored by Evil Martians" width="236" height="54">
+</a>
+
+[`postcss-current-selector`]: https://github.com/komlev/postcss-current-selector
+[`postcss-nested-ancestors`]: https://github.com/toomuchdesign/postcss-nested-ancestors
+[`postcss-nested-props`]: https://github.com/jedmao/postcss-nested-props
+[`postcss-nesting`]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting
+[CSSWG draft]: https://drafts.csswg.org/css-nesting-1/
+[PostCSS]: https://github.com/postcss/postcss
+
+
+## Docs
+Read full docs **[here](https://github.com/postcss/postcss-nested#readme)**.
diff --git a/node_modules/postcss-nested/index.d.ts b/node_modules/postcss-nested/index.d.ts
new file mode 100644
index 0000000..5367682
--- /dev/null
+++ b/node_modules/postcss-nested/index.d.ts
@@ -0,0 +1,41 @@
+// Original definitions (@types/postcss-nested)
+// by Maxim Vorontsov <https://github.com/VorontsovMaxim>
+
+import { PluginCreator } from 'postcss'
+
+declare namespace nested {
+ interface Options {
+ /**
+ * By default, plugin will bubble only `@media`, `@supports` and `@layer`
+ * at-rules. Use this option to add your custom at-rules to this list.
+ */
+ bubble?: string[]
+
+ /**
+ * By default, plugin will unwrap only `@font-face`, `@keyframes`,
+ * and `@document` at-rules. You can add your custom at-rules
+ * to this list by this option.
+ */
+ unwrap?: string[]
+
+ /**
+ * By default, plugin will strip out any empty selector generated
+ * by intermediate nesting levels. You can set this option to `true`
+ * to preserve them.
+ */
+ preserveEmpty?: boolean
+
+ /**
+ * The plugin supports the SCSS custom at-rule `@at-root` which breaks
+ * rule blocks out of their nested position. If you want, you can choose
+ * a new custom name for this rule in your code.
+ */
+ rootRuleName?: string
+ }
+
+ type Nested = PluginCreator<Options>
+}
+
+declare const nested: nested.Nested
+
+export = nested
diff --git a/node_modules/postcss-nested/index.js b/node_modules/postcss-nested/index.js
new file mode 100644
index 0000000..fe8618e
--- /dev/null
+++ b/node_modules/postcss-nested/index.js
@@ -0,0 +1,361 @@
+const { Rule, AtRule } = require('postcss')
+let parser = require('postcss-selector-parser')
+
+/**
+ * Run a selector string through postcss-selector-parser
+ */
+function parse(rawSelector, rule) {
+ let nodes
+ try {
+ parser(parsed => {
+ nodes = parsed
+ }).processSync(rawSelector)
+ } catch (e) {
+ if (rawSelector.includes(':')) {
+ throw rule ? rule.error('Missed semicolon') : e
+ } else {
+ throw rule ? rule.error(e.message) : e
+ }
+ }
+ return nodes.at(0)
+}
+
+/**
+ * Replaces the "&" token in a node's selector with the parent selector
+ * similar to what SCSS does.
+ *
+ * Mutates the nodes list
+ */
+function interpolateAmpInSelector(nodes, parent) {
+ let replaced = false
+ nodes.each(node => {
+ if (node.type === 'nesting') {
+ let clonedParent = parent.clone({})
+ if (node.value !== '&') {
+ node.replaceWith(
+ parse(node.value.replace('&', clonedParent.toString()))
+ )
+ } else {
+ node.replaceWith(clonedParent)
+ }
+ replaced = true
+ } else if ('nodes' in node && node.nodes) {
+ if (interpolateAmpInSelector(node, parent)) {
+ replaced = true
+ }
+ }
+ })
+ return replaced
+}
+
+/**
+ * Combines parent and child selectors, in a SCSS-like way
+ */
+function mergeSelectors(parent, child) {
+ let merged = []
+ parent.selectors.forEach(sel => {
+ let parentNode = parse(sel, parent)
+
+ child.selectors.forEach(selector => {
+ if (!selector) {
+ return
+ }
+ let node = parse(selector, child)
+ let replaced = interpolateAmpInSelector(node, parentNode)
+ if (!replaced) {
+ node.prepend(parser.combinator({ value: ' ' }))
+ node.prepend(parentNode.clone({}))
+ }
+ merged.push(node.toString())
+ })
+ })
+ return merged
+}
+
+/**
+ * Move a child and its preceeding comment(s) to after "after"
+ */
+function breakOut(child, after) {
+ let prev = child.prev()
+ after.after(child)
+ while (prev && prev.type === 'comment') {
+ let nextPrev = prev.prev()
+ after.after(prev)
+ prev = nextPrev
+ }
+ return child
+}
+
+function createFnAtruleChilds(bubble) {
+ return function atruleChilds(rule, atrule, bubbling, mergeSels = bubbling) {
+ let children = []
+ atrule.each(child => {
+ if (child.type === 'rule' && bubbling) {
+ if (mergeSels) {
+ child.selectors = mergeSelectors(rule, child)
+ }
+ } else if (child.type === 'atrule' && child.nodes) {
+ if (bubble[child.name]) {
+ atruleChilds(rule, child, mergeSels)
+ } else if (atrule[rootRuleMergeSel] !== false) {
+ children.push(child)
+ }
+ } else {
+ children.push(child)
+ }
+ })
+ if (bubbling) {
+ if (children.length) {
+ let clone = rule.clone({ nodes: [] })
+ for (let child of children) {
+ clone.append(child)
+ }
+ atrule.prepend(clone)
+ }
+ }
+ }
+}
+
+function pickDeclarations(selector, declarations, after) {
+ let parent = new Rule({
+ selector,
+ nodes: []
+ })
+ parent.append(declarations)
+ after.after(parent)
+ return parent
+}
+
+function atruleNames(defaults, custom) {
+ let list = {}
+ for (let name of defaults) {
+ list[name] = true
+ }
+ if (custom) {
+ for (let name of custom) {
+ list[name.replace(/^@/, '')] = true
+ }
+ }
+ return list
+}
+
+function parseRootRuleParams(params) {
+ params = params.trim()
+ let braceBlock = params.match(/^\((.*)\)$/)
+ if (!braceBlock) {
+ return { type: 'basic', selector: params }
+ }
+ let bits = braceBlock[1].match(/^(with(?:out)?):(.+)$/)
+ if (bits) {
+ let allowlist = bits[1] === 'with'
+ let rules = Object.fromEntries(
+ bits[2]
+ .trim()
+ .split(/\s+/)
+ .map(name => [name, true])
+ )
+ if (allowlist && rules.all) {
+ return { type: 'noop' }
+ }
+ let escapes = rule => !!rules[rule]
+ if (rules.all) {
+ escapes = () => true
+ } else if (allowlist) {
+ escapes = rule => (rule === 'all' ? false : !rules[rule])
+ }
+
+ return {
+ type: 'withrules',
+ escapes
+ }
+ }
+ // Unrecognized brace block
+ return { type: 'unknown' }
+}
+
+function getAncestorRules(leaf) {
+ let lineage = []
+ let parent = leaf.parent
+
+ while (parent && parent instanceof AtRule) {
+ lineage.push(parent)
+ parent = parent.parent
+ }
+ return lineage
+}
+
+function unwrapRootRule(rule) {
+ let escapes = rule[rootRuleEscapes]
+
+ if (!escapes) {
+ rule.after(rule.nodes)
+ } else {
+ let nodes = rule.nodes
+
+ let topEscaped
+ let topEscapedIdx = -1
+ let breakoutLeaf
+ let breakoutRoot
+ let clone
+
+ let lineage = getAncestorRules(rule)
+ lineage.forEach((parent, i) => {
+ if (escapes(parent.name)) {
+ topEscaped = parent
+ topEscapedIdx = i
+ breakoutRoot = clone
+ } else {
+ let oldClone = clone
+ clone = parent.clone({ nodes: [] })
+ oldClone && clone.append(oldClone)
+ breakoutLeaf = breakoutLeaf || clone
+ }
+ })
+
+ if (!topEscaped) {
+ rule.after(nodes)
+ } else if (!breakoutRoot) {
+ topEscaped.after(nodes)
+ } else {
+ let leaf = breakoutLeaf
+ leaf.append(nodes)
+ topEscaped.after(breakoutRoot)
+ }
+
+ if (rule.next() && topEscaped) {
+ let restRoot
+ lineage.slice(0, topEscapedIdx + 1).forEach((parent, i, arr) => {
+ let oldRoot = restRoot
+ restRoot = parent.clone({ nodes: [] })
+ oldRoot && restRoot.append(oldRoot)
+
+ let nextSibs = []
+ let _child = arr[i - 1] || rule
+ let next = _child.next()
+ while (next) {
+ nextSibs.push(next)
+ next = next.next()
+ }
+ restRoot.append(nextSibs)
+ })
+ restRoot && (breakoutRoot || nodes[nodes.length - 1]).after(restRoot)
+ }
+ }
+
+ rule.remove()
+}
+
+const rootRuleMergeSel = Symbol('rootRuleMergeSel')
+const rootRuleEscapes = Symbol('rootRuleEscapes')
+
+function normalizeRootRule(rule) {
+ let { params } = rule
+ let { type, selector, escapes } = parseRootRuleParams(params)
+ if (type === 'unknown') {
+ throw rule.error(
+ `Unknown @${rule.name} parameter ${JSON.stringify(params)}`
+ )
+ }
+ if (type === 'basic' && selector) {
+ let selectorBlock = new Rule({ selector, nodes: rule.nodes })
+ rule.removeAll()
+ rule.append(selectorBlock)
+ }
+ rule[rootRuleEscapes] = escapes
+ rule[rootRuleMergeSel] = escapes ? !escapes('all') : type === 'noop'
+}
+
+const hasRootRule = Symbol('hasRootRule')
+
+module.exports = (opts = {}) => {
+ let bubble = atruleNames(
+ ['media', 'supports', 'layer', 'container'],
+ opts.bubble
+ )
+ let atruleChilds = createFnAtruleChilds(bubble)
+ let unwrap = atruleNames(
+ [
+ 'document',
+ 'font-face',
+ 'keyframes',
+ '-webkit-keyframes',
+ '-moz-keyframes'
+ ],
+ opts.unwrap
+ )
+ let rootRuleName = (opts.rootRuleName || 'at-root').replace(/^@/, '')
+ let preserveEmpty = opts.preserveEmpty
+
+ return {
+ postcssPlugin: 'postcss-nested',
+
+ Once(root) {
+ root.walkAtRules(rootRuleName, node => {
+ normalizeRootRule(node)
+ root[hasRootRule] = true
+ })
+ },
+
+ Rule(rule) {
+ let unwrapped = false
+ let after = rule
+ let copyDeclarations = false
+ let declarations = []
+
+ rule.each(child => {
+ if (child.type === 'rule') {
+ if (declarations.length) {
+ after = pickDeclarations(rule.selector, declarations, after)
+ declarations = []
+ }
+
+ copyDeclarations = true
+ unwrapped = true
+ child.selectors = mergeSelectors(rule, child)
+ after = breakOut(child, after)
+ } else if (child.type === 'atrule') {
+ if (declarations.length) {
+ after = pickDeclarations(rule.selector, declarations, after)
+ declarations = []
+ }
+ if (child.name === rootRuleName) {
+ unwrapped = true
+ atruleChilds(rule, child, true, child[rootRuleMergeSel])
+ after = breakOut(child, after)
+ } else if (bubble[child.name]) {
+ copyDeclarations = true
+ unwrapped = true
+ atruleChilds(rule, child, true)
+ after = breakOut(child, after)
+ } else if (unwrap[child.name]) {
+ copyDeclarations = true
+ unwrapped = true
+ atruleChilds(rule, child, false)
+ after = breakOut(child, after)
+ } else if (copyDeclarations) {
+ declarations.push(child)
+ }
+ } else if (child.type === 'decl' && copyDeclarations) {
+ declarations.push(child)
+ }
+ })
+
+ if (declarations.length) {
+ after = pickDeclarations(rule.selector, declarations, after)
+ }
+
+ if (unwrapped && preserveEmpty !== true) {
+ rule.raws.semicolon = true
+ if (rule.nodes.length === 0) rule.remove()
+ }
+ },
+
+ RootExit(root) {
+ if (root[hasRootRule]) {
+ root.walkAtRules(rootRuleName, unwrapRootRule)
+ root[hasRootRule] = false
+ }
+ }
+ }
+}
+module.exports.postcss = true
diff --git a/node_modules/postcss-nested/package.json b/node_modules/postcss-nested/package.json
new file mode 100644
index 0000000..3c9d9ac
--- /dev/null
+++ b/node_modules/postcss-nested/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "postcss-nested",
+ "version": "6.0.1",
+ "description": "PostCSS plugin to unwrap nested rules like how Sass does it",
+ "keywords": [
+ "postcss",
+ "css",
+ "postcss-plugin",
+ "sass",
+ "nested"
+ ],
+ "author": "Andrey Sitnik <andrey@sitnik.ru>",
+ "license": "MIT",
+ "repository": "postcss/postcss-nested",
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ },
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ }
+}