@seyuna/postcss 1.0.0-canary.12 → 1.0.0-canary.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # [1.0.0-canary.14](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.13...v1.0.0-canary.14) (2025-10-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * bug in generateRules ([d5b66d3](https://github.com/seyuna-corp/seyuna-postcss/commit/d5b66d32786f554c0e4f7ea98ae526e7f7ac82c5))
7
+ * removed pnpm lock file that ([6d87d61](https://github.com/seyuna-corp/seyuna-postcss/commit/6d87d616a79a2a539b09dea26bc907dee1d437e2))
8
+ * updated lock file ([d52dcfb](https://github.com/seyuna-corp/seyuna-postcss/commit/d52dcfb2bccbd502d744ceb09aa83394df4ac057))
9
+
10
+ # [1.0.0-canary.13](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.12...v1.0.0-canary.13) (2025-10-07)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * safely clone nodes and replace {name} placeholders ([8a68ee6](https://github.com/seyuna-corp/seyuna-postcss/commit/8a68ee6f1ade947639151bdbf365c102aa439d37))
16
+
1
17
  # [1.0.0-canary.12](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.11...v1.0.0-canary.12) (2025-09-15)
2
18
 
3
19
 
@@ -1,34 +1,9 @@
1
1
  import { AtRule } from "postcss";
2
2
  /**
3
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
4
- *
5
- * Example usage:
6
- *
7
- * @each-standard-color {
8
- * color: white;
9
- * }
10
- *
11
- * Will generate:
12
- *
13
- * .alpha { color: white; }
14
- * .beta { color: white; }
15
- * .gamma { color: white; }
16
- * ...
3
+ * Handler for @each-standard-color
17
4
  */
18
5
  export declare function eachStandardColor(atRule: AtRule): void;
19
6
  /**
20
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
21
- *
22
- * Example usage:
23
- *
24
- * @each-fixed-color {
25
- * color: white;
26
- * }
27
- *
28
- * Will generate:
29
- *
30
- * .primary { color: white; }
31
- * .secondary { color: white; }
32
- * ...
7
+ * Handler for @each-fixed-color
33
8
  */
34
9
  export declare function eachFixedColor(atRule: AtRule): void;
@@ -5,156 +5,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.eachStandardColor = eachStandardColor;
7
7
  exports.eachFixedColor = eachFixedColor;
8
- const postcss_1 = require("postcss");
9
8
  const fs_1 = __importDefault(require("fs"));
10
9
  const path_1 = __importDefault(require("path"));
11
- const color_1 = require("../functions/color");
10
+ const helpers_1 = require("../helpers");
12
11
  /**
13
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
14
- *
15
- * Example usage:
16
- *
17
- * @each-standard-color {
18
- * color: white;
19
- * }
20
- *
21
- * Will generate:
22
- *
23
- * .alpha { color: white; }
24
- * .beta { color: white; }
25
- * .gamma { color: white; }
26
- * ...
12
+ * Handler for @each-standard-color
27
13
  */
28
14
  function eachStandardColor(atRule) {
29
- // Read seyuna.json from project root
30
15
  const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
31
- const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
32
- const data = JSON.parse(fileContents);
33
- const hues = data.ui.theme.hues;
34
- const hueNamesSet = new Set(Object.keys(hues));
35
- // Guard against atRule.nodes being undefined
36
- const nodes = atRule.nodes ?? [];
37
- const generatedRules = [];
38
- // Helper to clone nodes and replace {name} placeholder
39
- const cloneNodesWithName = (name, nodeList = nodes) => nodeList.map((node) => {
40
- const cloned = node.clone();
41
- if (cloned.type === "decl") {
42
- const decl = cloned;
43
- // First replace {name} placeholders
44
- let value = decl.value.replace(/\{name\}/g, name);
45
- // Detect sc(...) or fc(...) calls and evaluate them
46
- if (/sc\(/.test(value)) {
47
- const args = value
48
- .match(/sc\(([^)]*)\)/)?.[1]
49
- .split(",")
50
- .map((s) => s.trim().replace(/\{name\}/g, name));
51
- if (args)
52
- value = (0, color_1.sc)(...args);
53
- }
54
- if (/fc\(/.test(value)) {
55
- const args = value
56
- .match(/fc\(([^)]*)\)/)?.[1]
57
- .split(",")
58
- .map((s) => s.trim().replace(/\{name\}/g, name));
59
- if (args)
60
- value = (0, color_1.fc)(...args);
61
- }
62
- decl.value = value;
63
- }
64
- else if (cloned.type === "rule") {
65
- const rule = cloned;
66
- rule.selector = rule.selector.replace(/\{name\}/g, name);
67
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
68
- }
69
- else if (cloned.type === "atrule") {
70
- cloned.params = cloned.params.replace(/\{name\}/g, name);
71
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
72
- }
73
- return cloned;
74
- });
75
- // Generate rules for each hue
76
- for (const hueName of hueNamesSet) {
77
- const rule = new postcss_1.Rule({ selector: `.${hueName}` });
78
- cloneNodesWithName(hueName).forEach((n) => rule.append(n));
79
- generatedRules.push(rule);
80
- }
81
- // Replace the original @each-seyuna-color at-rule with all the generated rules
82
- atRule.replaceWith(...generatedRules);
16
+ const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
17
+ const hueNames = Object.keys(data.ui.theme.hues);
18
+ const rules = (0, helpers_1.generateRules)(hueNames, atRule);
19
+ if (rules.length)
20
+ atRule.replaceWith(...rules);
21
+ else
22
+ atRule.remove();
83
23
  }
84
24
  /**
85
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
86
- *
87
- * Example usage:
88
- *
89
- * @each-fixed-color {
90
- * color: white;
91
- * }
92
- *
93
- * Will generate:
94
- *
95
- * .primary { color: white; }
96
- * .secondary { color: white; }
97
- * ...
25
+ * Handler for @each-fixed-color
98
26
  */
99
27
  function eachFixedColor(atRule) {
100
- // Read seyuna.json from project root
101
28
  const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
102
- const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
103
- const data = JSON.parse(fileContents);
104
- const light_colors = data.ui.theme.light.colors;
105
- const dark_colors = data.ui.theme.dark.colors;
106
- const lightColorNamesSet = new Set(Object.keys(light_colors));
107
- const darkColorNamesSet = new Set(Object.keys(dark_colors));
108
- const mergedColorNamesSet = new Set([
109
- ...lightColorNamesSet,
110
- ...darkColorNamesSet,
111
- ]);
112
- // Guard against atRule.nodes being undefined
113
- const nodes = atRule.nodes ?? [];
114
- const generatedRules = [];
115
- // Helper to clone nodes and replace {name} placeholder
116
- const cloneNodesWithName = (name, nodeList = nodes) => nodeList.map((node) => {
117
- const cloned = node.clone();
118
- if (cloned.type === "decl") {
119
- const decl = cloned;
120
- // First replace {name} placeholders
121
- let value = decl.value.replace(/\{name\}/g, name);
122
- // Detect sc(...) or fc(...) calls and evaluate them
123
- if (/sc\(/.test(value)) {
124
- const args = value
125
- .match(/sc\(([^)]*)\)/)?.[1]
126
- .split(",")
127
- .map((s) => s.trim().replace(/\{name\}/g, name));
128
- if (args)
129
- value = (0, color_1.sc)(...args);
130
- }
131
- if (/fc\(/.test(value)) {
132
- const args = value
133
- .match(/fc\(([^)]*)\)/)?.[1]
134
- .split(",")
135
- .map((s) => s.trim().replace(/\{name\}/g, name));
136
- if (args)
137
- value = (0, color_1.fc)(...args);
138
- }
139
- decl.value = value;
140
- }
141
- else if (cloned.type === "rule") {
142
- const rule = cloned;
143
- rule.selector = rule.selector.replace(/\{name\}/g, name);
144
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
145
- }
146
- else if (cloned.type === "atrule") {
147
- cloned.params = cloned.params.replace(/\{name\}/g, name);
148
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
149
- }
150
- return cloned;
151
- });
152
- // Generate rules for mergedColorNamesSet
153
- for (const colorName of mergedColorNamesSet) {
154
- const rule = new postcss_1.Rule({ selector: `.${colorName}` });
155
- cloneNodesWithName(colorName).forEach((n) => rule.append(n));
156
- generatedRules.push(rule);
157
- }
158
- // Replace the original @each-seyuna-color at-rule with all the generated rules
159
- atRule.replaceWith(...generatedRules);
29
+ const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
30
+ const mergedNames = [
31
+ ...new Set([
32
+ ...Object.keys(data.ui.theme.light.colors),
33
+ ...Object.keys(data.ui.theme.dark.colors),
34
+ ]),
35
+ ];
36
+ const rules = (0, helpers_1.generateRules)(mergedNames, atRule);
37
+ if (rules.length)
38
+ atRule.replaceWith(...rules);
39
+ else
40
+ atRule.remove();
160
41
  }
@@ -0,0 +1,10 @@
1
+ import { Rule, ChildNode, AtRule } from "postcss";
2
+ /**
3
+ * Helper: clone nodes and replace {name} placeholders safely
4
+ * Returns only valid Rules or Declarations (never raw AtRules)
5
+ */
6
+ export declare function cloneNodes(nodes: ChildNode[], name: string): ChildNode[];
7
+ /**
8
+ * Generate CSS rules from a list of names
9
+ */
10
+ export declare function generateRules(names: string[], atRule: AtRule): Rule[];
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cloneNodes = cloneNodes;
4
+ exports.generateRules = generateRules;
5
+ const postcss_1 = require("postcss");
6
+ const color_1 = require("./functions/color");
7
+ /**
8
+ * Helper: clone nodes and replace {name} placeholders safely
9
+ * Returns only valid Rules or Declarations (never raw AtRules)
10
+ */
11
+ function cloneNodes(nodes, name) {
12
+ return nodes.flatMap((node) => {
13
+ const cloned = node.clone();
14
+ if (cloned.type === "decl") {
15
+ const decl = cloned;
16
+ let value = decl.value.replace(/\{name\}/g, name);
17
+ if (/sc\(/.test(value)) {
18
+ const args = value
19
+ .match(/sc\(([^)]*)\)/)?.[1]
20
+ .split(",")
21
+ .map((s) => s.trim());
22
+ if (args)
23
+ value = (0, color_1.sc)(...args);
24
+ }
25
+ if (/fc\(/.test(value)) {
26
+ const args = value
27
+ .match(/fc\(([^)]*)\)/)?.[1]
28
+ .split(",")
29
+ .map((s) => s.trim());
30
+ if (args)
31
+ value = (0, color_1.fc)(...args);
32
+ }
33
+ decl.value = value;
34
+ return decl;
35
+ }
36
+ if (cloned.type === "rule") {
37
+ const rule = cloned;
38
+ if (!rule.selector)
39
+ return [];
40
+ rule.selector = rule.selector.replace(/\{name\}/g, name);
41
+ // Recursively clone child nodes and only keep valid rules/decls
42
+ rule.nodes = cloneNodes(rule.nodes || [], name).filter((n) => n.type === "rule" || n.type === "decl");
43
+ if (!rule.nodes.length)
44
+ return [];
45
+ return rule;
46
+ }
47
+ // Ignore AtRules inside rules — they must be processed first
48
+ return [];
49
+ });
50
+ }
51
+ /**
52
+ * Generate CSS rules from a list of names
53
+ */
54
+ function generateRules(names, atRule) {
55
+ const nodes = atRule.nodes ?? [];
56
+ const generatedRules = [];
57
+ for (const name of names) {
58
+ const rule = new postcss_1.Rule({ selector: `&.${name}` });
59
+ cloneNodes(nodes, name).forEach((n) => {
60
+ if (n.type === "rule" && n.selector && n.nodes?.length)
61
+ rule.append(n);
62
+ if (n.type === "decl")
63
+ rule.append(n);
64
+ });
65
+ if (rule.nodes.length)
66
+ generatedRules.push(rule);
67
+ }
68
+ return generatedRules;
69
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@seyuna/postcss",
3
- "version": "1.0.0-canary.12",
3
+ "version": "1.0.0-canary.14",
4
4
  "description": "Seyuna UI's postcss plugin",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
- "build": "tsc"
8
+ "build": "tsc",
9
+ "dev": "tsc -w"
9
10
  },
10
11
  "keywords": [
11
12
  "postcss",
@@ -30,6 +31,7 @@
30
31
  "@semantic-release/release-notes-generator": "^14.0.3",
31
32
  "@types/node": "^20.0.0",
32
33
  "postcss": "^8.5.6",
34
+ "postcss-selector-parser": "^7.1.0",
33
35
  "semantic-release": "^24.2.7",
34
36
  "typescript": "^5.0.0"
35
37
  }
@@ -2,182 +2,36 @@ import { AtRule, Rule, ChildNode, Declaration } from "postcss";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import { SeyunaConfig } from "../types";
5
- import { fc, sc } from "../functions/color";
5
+ import { generateRules } from "../helpers";
6
6
 
7
7
  /**
8
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
9
- *
10
- * Example usage:
11
- *
12
- * @each-standard-color {
13
- * color: white;
14
- * }
15
- *
16
- * Will generate:
17
- *
18
- * .alpha { color: white; }
19
- * .beta { color: white; }
20
- * .gamma { color: white; }
21
- * ...
8
+ * Handler for @each-standard-color
22
9
  */
23
10
  export function eachStandardColor(atRule: AtRule) {
24
- // Read seyuna.json from project root
25
11
  const jsonPath = path.resolve(process.cwd(), "seyuna.json");
26
- const fileContents = fs.readFileSync(jsonPath, "utf-8");
27
- const data: SeyunaConfig = JSON.parse(fileContents);
28
- const hues = data.ui.theme.hues;
29
- const hueNamesSet = new Set(Object.keys(hues));
12
+ const data: SeyunaConfig = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
13
+ const hueNames = Object.keys(data.ui.theme.hues);
30
14
 
31
- // Guard against atRule.nodes being undefined
32
- const nodes = atRule.nodes ?? [];
33
-
34
- const generatedRules: Rule[] = [];
35
-
36
- // Helper to clone nodes and replace {name} placeholder
37
- const cloneNodesWithName = (
38
- name: string,
39
- nodeList: ChildNode[] = nodes
40
- ): ChildNode[] =>
41
- nodeList.map((node) => {
42
- const cloned = node.clone();
43
-
44
- if (cloned.type === "decl") {
45
- const decl = cloned as Declaration;
46
-
47
- // First replace {name} placeholders
48
- let value = decl.value.replace(/\{name\}/g, name);
49
-
50
- // Detect sc(...) or fc(...) calls and evaluate them
51
- if (/sc\(/.test(value)) {
52
- const args = value
53
- .match(/sc\(([^)]*)\)/)?.[1]
54
- .split(",")
55
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
56
- if (args)
57
- value = sc(...(args as [string, string?, string?, string?]));
58
- }
59
-
60
- if (/fc\(/.test(value)) {
61
- const args = value
62
- .match(/fc\(([^)]*)\)/)?.[1]
63
- .split(",")
64
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
65
- if (args)
66
- value = fc(...(args as [string, string?, string?, string?]));
67
- }
68
-
69
- decl.value = value;
70
- } else if (cloned.type === "rule") {
71
- const rule = cloned as Rule;
72
- rule.selector = rule.selector.replace(/\{name\}/g, name);
73
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
74
- } else if (cloned.type === "atrule") {
75
- cloned.params = cloned.params.replace(/\{name\}/g, name);
76
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
77
- }
78
-
79
- return cloned;
80
- });
81
-
82
- // Generate rules for each hue
83
- for (const hueName of hueNamesSet) {
84
- const rule = new Rule({ selector: `.${hueName}` });
85
- cloneNodesWithName(hueName).forEach((n) => rule.append(n));
86
- generatedRules.push(rule);
87
- }
88
-
89
- // Replace the original @each-seyuna-color at-rule with all the generated rules
90
- atRule.replaceWith(...generatedRules);
15
+ const rules = generateRules(hueNames, atRule);
16
+ if (rules.length) atRule.replaceWith(...rules);
17
+ else atRule.remove();
91
18
  }
92
19
 
93
20
  /**
94
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
95
- *
96
- * Example usage:
97
- *
98
- * @each-fixed-color {
99
- * color: white;
100
- * }
101
- *
102
- * Will generate:
103
- *
104
- * .primary { color: white; }
105
- * .secondary { color: white; }
106
- * ...
21
+ * Handler for @each-fixed-color
107
22
  */
108
23
  export function eachFixedColor(atRule: AtRule) {
109
- // Read seyuna.json from project root
110
24
  const jsonPath = path.resolve(process.cwd(), "seyuna.json");
111
- const fileContents = fs.readFileSync(jsonPath, "utf-8");
112
- const data: SeyunaConfig = JSON.parse(fileContents);
113
- const light_colors = data.ui.theme.light.colors;
114
- const dark_colors = data.ui.theme.dark.colors;
115
- const lightColorNamesSet = new Set(Object.keys(light_colors));
116
- const darkColorNamesSet = new Set(Object.keys(dark_colors));
117
-
118
- const mergedColorNamesSet = new Set([
119
- ...lightColorNamesSet,
120
- ...darkColorNamesSet,
121
- ]);
122
-
123
- // Guard against atRule.nodes being undefined
124
- const nodes = atRule.nodes ?? [];
125
-
126
- const generatedRules: Rule[] = [];
127
-
128
- // Helper to clone nodes and replace {name} placeholder
129
- const cloneNodesWithName = (
130
- name: string,
131
- nodeList: ChildNode[] = nodes
132
- ): ChildNode[] =>
133
- nodeList.map((node) => {
134
- const cloned = node.clone();
135
-
136
- if (cloned.type === "decl") {
137
- const decl = cloned as Declaration;
138
-
139
- // First replace {name} placeholders
140
- let value = decl.value.replace(/\{name\}/g, name);
141
-
142
- // Detect sc(...) or fc(...) calls and evaluate them
143
- if (/sc\(/.test(value)) {
144
- const args = value
145
- .match(/sc\(([^)]*)\)/)?.[1]
146
- .split(",")
147
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
148
- if (args)
149
- value = sc(...(args as [string, string?, string?, string?]));
150
- }
151
-
152
- if (/fc\(/.test(value)) {
153
- const args = value
154
- .match(/fc\(([^)]*)\)/)?.[1]
155
- .split(",")
156
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
157
- if (args)
158
- value = fc(...(args as [string, string?, string?, string?]));
159
- }
160
-
161
- decl.value = value;
162
- } else if (cloned.type === "rule") {
163
- const rule = cloned as Rule;
164
- rule.selector = rule.selector.replace(/\{name\}/g, name);
165
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
166
- } else if (cloned.type === "atrule") {
167
- cloned.params = cloned.params.replace(/\{name\}/g, name);
168
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
169
- }
170
-
171
- return cloned;
172
- });
173
-
174
- // Generate rules for mergedColorNamesSet
175
- for (const colorName of mergedColorNamesSet) {
176
- const rule = new Rule({ selector: `.${colorName}` });
177
- cloneNodesWithName(colorName).forEach((n) => rule.append(n));
178
- generatedRules.push(rule);
179
- }
180
-
181
- // Replace the original @each-seyuna-color at-rule with all the generated rules
182
- atRule.replaceWith(...generatedRules);
25
+ const data: SeyunaConfig = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
26
+
27
+ const mergedNames = [
28
+ ...new Set([
29
+ ...Object.keys(data.ui.theme.light.colors),
30
+ ...Object.keys(data.ui.theme.dark.colors),
31
+ ]),
32
+ ];
33
+
34
+ const rules = generateRules(mergedNames, atRule);
35
+ if (rules.length) atRule.replaceWith(...rules);
36
+ else atRule.remove();
183
37
  }
package/src/helpers.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { Rule, ChildNode, Declaration, AtRule } from "postcss";
2
+ import { fc, sc } from "./functions/color";
3
+
4
+ /**
5
+ * Helper: clone nodes and replace {name} placeholders safely
6
+ * Returns only valid Rules or Declarations (never raw AtRules)
7
+ */
8
+ export function cloneNodes(nodes: ChildNode[], name: string): ChildNode[] {
9
+ return nodes.flatMap((node) => {
10
+ const cloned = node.clone();
11
+
12
+ if (cloned.type === "decl") {
13
+ const decl = cloned as Declaration;
14
+ let value = decl.value.replace(/\{name\}/g, name);
15
+
16
+ if (/sc\(/.test(value)) {
17
+ const args = value
18
+ .match(/sc\(([^)]*)\)/)?.[1]
19
+ .split(",")
20
+ .map((s) => s.trim());
21
+ if (args) value = sc(...(args as [string, string?, string?, string?]));
22
+ }
23
+ if (/fc\(/.test(value)) {
24
+ const args = value
25
+ .match(/fc\(([^)]*)\)/)?.[1]
26
+ .split(",")
27
+ .map((s) => s.trim());
28
+ if (args) value = fc(...(args as [string, string?, string?, string?]));
29
+ }
30
+
31
+ decl.value = value;
32
+ return decl;
33
+ }
34
+
35
+ if (cloned.type === "rule") {
36
+ const rule = cloned as Rule;
37
+ if (!rule.selector) return [];
38
+
39
+ rule.selector = rule.selector.replace(/\{name\}/g, name);
40
+
41
+ // Recursively clone child nodes and only keep valid rules/decls
42
+ rule.nodes = cloneNodes(rule.nodes || [], name).filter(
43
+ (n) => n.type === "rule" || n.type === "decl"
44
+ );
45
+
46
+ if (!rule.nodes.length) return [];
47
+ return rule;
48
+ }
49
+
50
+ // Ignore AtRules inside rules — they must be processed first
51
+ return [];
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Generate CSS rules from a list of names
57
+ */
58
+ export function generateRules(names: string[], atRule: AtRule): Rule[] {
59
+ const nodes = atRule.nodes ?? [];
60
+ const generatedRules: Rule[] = [];
61
+
62
+ for (const name of names) {
63
+ const rule = new Rule({ selector: `&.${name}` });
64
+ cloneNodes(nodes, name).forEach((n) => {
65
+ if (n.type === "rule" && n.selector && n.nodes?.length) rule.append(n);
66
+ if (n.type === "decl") rule.append(n);
67
+ });
68
+
69
+ if (rule.nodes.length) generatedRules.push(rule);
70
+ }
71
+
72
+ return generatedRules;
73
+ }