@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 +16 -0
- package/dist/at-rules/color.d.ts +2 -27
- package/dist/at-rules/color.js +22 -141
- package/dist/helpers.d.ts +10 -0
- package/dist/helpers.js +69 -0
- package/package.json +4 -2
- package/src/at-rules/color.ts +20 -166
- package/src/helpers.ts +73 -0
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
|
|
package/dist/at-rules/color.d.ts
CHANGED
|
@@ -1,34 +1,9 @@
|
|
|
1
1
|
import { AtRule } from "postcss";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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;
|
package/dist/at-rules/color.js
CHANGED
|
@@ -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
|
|
10
|
+
const helpers_1 = require("../helpers");
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
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
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
*
|
|
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
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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[];
|
package/dist/helpers.js
ADDED
|
@@ -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.
|
|
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
|
}
|
package/src/at-rules/color.ts
CHANGED
|
@@ -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 {
|
|
5
|
+
import { generateRules } from "../helpers";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
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
|
|
27
|
-
const
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
*
|
|
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
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|