@seyuna/postcss 1.0.0-canary.26 → 1.0.0-canary.27
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 +7 -0
- package/dist/at-rules/import.d.ts +6 -0
- package/dist/at-rules/import.js +120 -0
- package/dist/at-rules/index.js +2 -0
- package/package.json +1 -1
- package/src/at-rules/import.ts +136 -0
- package/src/at-rules/index.ts +2 -0
- package/tests/plugin.test.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.0.0-canary.27](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.26...v1.0.0-canary.27) (2026-01-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* implement [@import](https://github.com/import) 'seyuna' to replicate CLI behavior ([db3a3ff](https://github.com/seyuna-corp/seyuna-postcss/commit/db3a3fff3664b435840e7421d9b99262dfd31953))
|
|
7
|
+
|
|
1
8
|
# [1.0.0-canary.26](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.25...v1.0.0-canary.26) (2026-01-11)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import Rule from "postcss/lib/rule";
|
|
2
|
+
import Declaration from "postcss/lib/declaration";
|
|
3
|
+
import AtRule from "postcss/lib/at-rule";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import postcss from "postcss";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const STANDARD_HUES = [
|
|
11
|
+
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Handler for @import "seyuna"
|
|
15
|
+
* Injects the core Seyuna Design System variables and base styles
|
|
16
|
+
*/
|
|
17
|
+
export function handleImport(atRule, context) {
|
|
18
|
+
const params = atRule.params.replace(/['"]/g, "");
|
|
19
|
+
if (params === "seyuna") {
|
|
20
|
+
const { config, options } = context;
|
|
21
|
+
if (!config.ui) {
|
|
22
|
+
atRule.remove();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { theme } = config.ui;
|
|
26
|
+
const modeAttribute = options.modeAttribute;
|
|
27
|
+
const nodes = [];
|
|
28
|
+
// 1. Ingest Global Base Styles (Reset, Layers, etc.)
|
|
29
|
+
try {
|
|
30
|
+
const globalStylesPath = path.resolve(__dirname, "../styles/seyuna-global.css");
|
|
31
|
+
if (fs.existsSync(globalStylesPath)) {
|
|
32
|
+
const globalCss = fs.readFileSync(globalStylesPath, "utf-8");
|
|
33
|
+
const cleanCss = globalCss.split('$')[0].trim();
|
|
34
|
+
const parsed = postcss.parse(cleanCss);
|
|
35
|
+
// Only keep layers and reset styles, avoid re-injecting variables
|
|
36
|
+
parsed.each((node) => {
|
|
37
|
+
if (node.type === 'atrule' && (node.name === 'layer' || node.name === 'media')) {
|
|
38
|
+
nodes.push(node.clone());
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
nodes.push(new AtRule({ name: "layer", params: "reset, base, components, utilities" }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
console.warn("[Seyuna PostCSS] Could not load global base styles:", e);
|
|
48
|
+
}
|
|
49
|
+
// 2. Global Hues and Base Colors (:root)
|
|
50
|
+
const rootRule = new Rule({ selector: ":root", source: atRule.source });
|
|
51
|
+
// Process all hues from config plus defaults
|
|
52
|
+
const allHues = new Set([...STANDARD_HUES, ...Object.keys(theme.hues || {})]);
|
|
53
|
+
allHues.forEach((name) => {
|
|
54
|
+
const i = STANDARD_HUES.indexOf(name);
|
|
55
|
+
const defaultValue = i !== -1 ? i * 15 : 0;
|
|
56
|
+
const value = (theme.hues && theme.hues[name] !== undefined) ? theme.hues[name] : defaultValue;
|
|
57
|
+
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
|
|
58
|
+
});
|
|
59
|
+
// Add shared colors from theme.colors
|
|
60
|
+
if (theme.colors) {
|
|
61
|
+
for (const [name, color] of Object.entries(theme.colors)) {
|
|
62
|
+
rootRule.append(new Declaration({ prop: `--${name}-lightness`, value: String(color.lightness) }));
|
|
63
|
+
rootRule.append(new Declaration({ prop: `--${name}-chroma`, value: String(color.chroma) }));
|
|
64
|
+
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(color.hue) }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
nodes.push(rootRule);
|
|
68
|
+
// Helper to generate palette declarations for a specific mode
|
|
69
|
+
const getPaletteDecls = (palette) => {
|
|
70
|
+
const decls = [];
|
|
71
|
+
if (palette.lightness !== undefined) {
|
|
72
|
+
decls.push(new Declaration({ prop: "--lightness", value: String(palette.lightness) }));
|
|
73
|
+
}
|
|
74
|
+
if (palette.chroma !== undefined) {
|
|
75
|
+
decls.push(new Declaration({ prop: "--chroma", value: String(palette.chroma) }));
|
|
76
|
+
}
|
|
77
|
+
// Standard background/text if they exist
|
|
78
|
+
if (palette.background) {
|
|
79
|
+
decls.push(new Declaration({ prop: "--background", value: `oklch(${palette.background.lightness} ${palette.background.chroma} ${palette.background.hue})` }));
|
|
80
|
+
}
|
|
81
|
+
if (palette.text) {
|
|
82
|
+
decls.push(new Declaration({ prop: "--text", value: `oklch(${palette.text.lightness} ${palette.text.chroma} ${palette.text.hue})` }));
|
|
83
|
+
}
|
|
84
|
+
if (palette.colors) {
|
|
85
|
+
for (const [name, color] of Object.entries(palette.colors)) {
|
|
86
|
+
const c = color;
|
|
87
|
+
decls.push(new Declaration({ prop: `--${name}-lightness`, value: String(c.lightness) }));
|
|
88
|
+
decls.push(new Declaration({ prop: `--${name}-chroma`, value: String(c.chroma) }));
|
|
89
|
+
decls.push(new Declaration({ prop: `--${name}-hue`, value: String(c.hue) }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return decls;
|
|
93
|
+
};
|
|
94
|
+
// 3. Explicit Mode Selectors ([data-mode="..."])
|
|
95
|
+
if (theme.light) {
|
|
96
|
+
const lightRule = new Rule({ selector: `[${modeAttribute}="light"]`, source: atRule.source });
|
|
97
|
+
lightRule.append(...getPaletteDecls(theme.light));
|
|
98
|
+
nodes.push(lightRule);
|
|
99
|
+
}
|
|
100
|
+
if (theme.dark) {
|
|
101
|
+
const darkRule = new Rule({ selector: `[${modeAttribute}="dark"]`, source: atRule.source });
|
|
102
|
+
darkRule.append(...getPaletteDecls(theme.dark));
|
|
103
|
+
nodes.push(darkRule);
|
|
104
|
+
}
|
|
105
|
+
// 4. System Preference Support
|
|
106
|
+
const createSystemMedia = (scheme, palette) => {
|
|
107
|
+
const media = new AtRule({ name: "media", params: `(prefers-color-scheme: ${scheme})`, source: atRule.source });
|
|
108
|
+
const rule = new Rule({ selector: `[${modeAttribute}="system"]`, source: atRule.source });
|
|
109
|
+
rule.append(...getPaletteDecls(palette));
|
|
110
|
+
media.append(rule);
|
|
111
|
+
return media;
|
|
112
|
+
};
|
|
113
|
+
if (theme.light)
|
|
114
|
+
nodes.push(createSystemMedia('light', theme.light));
|
|
115
|
+
if (theme.dark)
|
|
116
|
+
nodes.push(createSystemMedia('dark', theme.dark));
|
|
117
|
+
// Replace the @import rule with our generated CSS
|
|
118
|
+
atRule.replaceWith(...nodes);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/at-rules/index.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { eachStandardColor, eachFixedColor } from "./color.js";
|
|
3
3
|
import container from "./container.js";
|
|
4
4
|
import { light, dark } from "./color-scheme.js";
|
|
5
|
+
import { handleImport } from "./import.js";
|
|
5
6
|
// Ordered array ensures execution order
|
|
6
7
|
export const atRuleHandlers = [
|
|
8
|
+
{ name: "import", handler: handleImport },
|
|
7
9
|
{ name: "each-standard-color", handler: eachStandardColor },
|
|
8
10
|
{ name: "each-fixed-color", handler: eachFixedColor },
|
|
9
11
|
{ name: "light", handler: light },
|
package/package.json
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Rule from "postcss/lib/rule";
|
|
2
|
+
import Declaration from "postcss/lib/declaration";
|
|
3
|
+
import AtRule from "postcss/lib/at-rule";
|
|
4
|
+
import { PluginContext } from "../types.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import postcss from "postcss";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const STANDARD_HUES = [
|
|
14
|
+
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handler for @import "seyuna"
|
|
19
|
+
* Injects the core Seyuna Design System variables and base styles
|
|
20
|
+
*/
|
|
21
|
+
export function handleImport(atRule: any, context: PluginContext) {
|
|
22
|
+
const params = atRule.params.replace(/['"]/g, "");
|
|
23
|
+
|
|
24
|
+
if (params === "seyuna") {
|
|
25
|
+
const { config, options } = context;
|
|
26
|
+
if (!config.ui) {
|
|
27
|
+
atRule.remove();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { theme } = config.ui;
|
|
32
|
+
const modeAttribute = options.modeAttribute;
|
|
33
|
+
const nodes: any[] = [];
|
|
34
|
+
|
|
35
|
+
// 1. Ingest Global Base Styles (Reset, Layers, etc.)
|
|
36
|
+
try {
|
|
37
|
+
const globalStylesPath = path.resolve(__dirname, "../styles/seyuna-global.css");
|
|
38
|
+
if (fs.existsSync(globalStylesPath)) {
|
|
39
|
+
const globalCss = fs.readFileSync(globalStylesPath, "utf-8");
|
|
40
|
+
const cleanCss = globalCss.split('$')[0].trim();
|
|
41
|
+
const parsed = postcss.parse(cleanCss);
|
|
42
|
+
|
|
43
|
+
// Only keep layers and reset styles, avoid re-injecting variables
|
|
44
|
+
parsed.each((node) => {
|
|
45
|
+
if (node.type === 'atrule' && (node.name === 'layer' || node.name === 'media')) {
|
|
46
|
+
nodes.push(node.clone());
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
nodes.push(new AtRule({ name: "layer", params: "reset, base, components, utilities" }));
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.warn("[Seyuna PostCSS] Could not load global base styles:", e);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Global Hues and Base Colors (:root)
|
|
57
|
+
const rootRule = new Rule({ selector: ":root", source: atRule.source });
|
|
58
|
+
|
|
59
|
+
// Process all hues from config plus defaults
|
|
60
|
+
const allHues = new Set([...STANDARD_HUES, ...Object.keys(theme.hues || {})]);
|
|
61
|
+
allHues.forEach((name) => {
|
|
62
|
+
const i = STANDARD_HUES.indexOf(name);
|
|
63
|
+
const defaultValue = i !== -1 ? i * 15 : 0;
|
|
64
|
+
const value = (theme.hues && theme.hues[name] !== undefined) ? theme.hues[name] : defaultValue;
|
|
65
|
+
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Add shared colors from theme.colors
|
|
69
|
+
if (theme.colors) {
|
|
70
|
+
for (const [name, color] of Object.entries(theme.colors)) {
|
|
71
|
+
rootRule.append(new Declaration({ prop: `--${name}-lightness`, value: String(color.lightness) }));
|
|
72
|
+
rootRule.append(new Declaration({ prop: `--${name}-chroma`, value: String(color.chroma) }));
|
|
73
|
+
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(color.hue) }));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
nodes.push(rootRule);
|
|
77
|
+
|
|
78
|
+
// Helper to generate palette declarations for a specific mode
|
|
79
|
+
const getPaletteDecls = (palette: any) => {
|
|
80
|
+
const decls: Declaration[] = [];
|
|
81
|
+
|
|
82
|
+
if (palette.lightness !== undefined) {
|
|
83
|
+
decls.push(new Declaration({ prop: "--lightness", value: String(palette.lightness) }));
|
|
84
|
+
}
|
|
85
|
+
if (palette.chroma !== undefined) {
|
|
86
|
+
decls.push(new Declaration({ prop: "--chroma", value: String(palette.chroma) }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Standard background/text if they exist
|
|
90
|
+
if (palette.background) {
|
|
91
|
+
decls.push(new Declaration({ prop: "--background", value: `oklch(${palette.background.lightness} ${palette.background.chroma} ${palette.background.hue})` }));
|
|
92
|
+
}
|
|
93
|
+
if (palette.text) {
|
|
94
|
+
decls.push(new Declaration({ prop: "--text", value: `oklch(${palette.text.lightness} ${palette.text.chroma} ${palette.text.hue})` }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (palette.colors) {
|
|
98
|
+
for (const [name, color] of Object.entries(palette.colors)) {
|
|
99
|
+
const c = color as any;
|
|
100
|
+
decls.push(new Declaration({ prop: `--${name}-lightness`, value: String(c.lightness) }));
|
|
101
|
+
decls.push(new Declaration({ prop: `--${name}-chroma`, value: String(c.chroma) }));
|
|
102
|
+
decls.push(new Declaration({ prop: `--${name}-hue`, value: String(c.hue) }));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return decls;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 3. Explicit Mode Selectors ([data-mode="..."])
|
|
109
|
+
if (theme.light) {
|
|
110
|
+
const lightRule = new Rule({ selector: `[${modeAttribute}="light"]`, source: atRule.source });
|
|
111
|
+
lightRule.append(...getPaletteDecls(theme.light));
|
|
112
|
+
nodes.push(lightRule);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (theme.dark) {
|
|
116
|
+
const darkRule = new Rule({ selector: `[${modeAttribute}="dark"]`, source: atRule.source });
|
|
117
|
+
darkRule.append(...getPaletteDecls(theme.dark));
|
|
118
|
+
nodes.push(darkRule);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 4. System Preference Support
|
|
122
|
+
const createSystemMedia = (scheme: 'light' | 'dark', palette: any) => {
|
|
123
|
+
const media = new AtRule({ name: "media", params: `(prefers-color-scheme: ${scheme})`, source: atRule.source });
|
|
124
|
+
const rule = new Rule({ selector: `[${modeAttribute}="system"]`, source: atRule.source });
|
|
125
|
+
rule.append(...getPaletteDecls(palette));
|
|
126
|
+
media.append(rule);
|
|
127
|
+
return media;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (theme.light) nodes.push(createSystemMedia('light', theme.light));
|
|
131
|
+
if (theme.dark) nodes.push(createSystemMedia('dark', theme.dark));
|
|
132
|
+
|
|
133
|
+
// Replace the @import rule with our generated CSS
|
|
134
|
+
atRule.replaceWith(...nodes);
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/at-rules/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { eachStandardColor, eachFixedColor } from "./color.js";
|
|
3
3
|
import container from "./container.js";
|
|
4
4
|
import { light, dark } from "./color-scheme.js";
|
|
5
|
+
import { handleImport } from "./import.js";
|
|
5
6
|
import { PluginContext } from "../types.js";
|
|
6
7
|
|
|
7
8
|
// Each handler has a name (matches the at-rule) and the function
|
|
@@ -12,6 +13,7 @@ export interface AtRuleHandler {
|
|
|
12
13
|
|
|
13
14
|
// Ordered array ensures execution order
|
|
14
15
|
export const atRuleHandlers: AtRuleHandler[] = [
|
|
16
|
+
{ name: "import", handler: handleImport },
|
|
15
17
|
{ name: "each-standard-color", handler: eachStandardColor },
|
|
16
18
|
{ name: "each-fixed-color", handler: eachFixedColor },
|
|
17
19
|
{ name: "light", handler: light },
|
package/tests/plugin.test.ts
CHANGED
|
@@ -140,4 +140,31 @@ describe('Seyuna PostCSS Plugin', () => {
|
|
|
140
140
|
await expect(run(input, { config: mockConfig, strict: true }))
|
|
141
141
|
.rejects.toThrow(/Color 'unknown' not found in seyuna.json/);
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
it('processes @import "seyuna"', async () => {
|
|
145
|
+
const input = '@import "seyuna";';
|
|
146
|
+
const result = await run(input, { config: mockConfig });
|
|
147
|
+
|
|
148
|
+
// Check for root variables (hues)
|
|
149
|
+
expect(result.css).toContain(':root');
|
|
150
|
+
expect(result.css).toContain('--alpha-hue: 0');
|
|
151
|
+
expect(result.css).toContain('--omega-hue: 345');
|
|
152
|
+
expect(result.css).toContain('--primary-hue: 200'); // Override from mockConfig
|
|
153
|
+
|
|
154
|
+
// Check for mode selectors
|
|
155
|
+
expect(result.css).toContain('[data-mode="light"]');
|
|
156
|
+
expect(result.css).toContain('[data-mode="dark"]');
|
|
157
|
+
expect(result.css).toContain('--lightness: 0.66');
|
|
158
|
+
|
|
159
|
+
// Check for system preference media queries
|
|
160
|
+
expect(result.css).toContain('@media (prefers-color-scheme: light)');
|
|
161
|
+
expect(result.css).toContain('@media (prefers-color-scheme: dark)');
|
|
162
|
+
|
|
163
|
+
// Check for base styles (reset)
|
|
164
|
+
expect(result.css).toContain('@layer reset');
|
|
165
|
+
expect(result.css).toContain('-webkit-text-size-adjust: none');
|
|
166
|
+
|
|
167
|
+
// Check for palette colors
|
|
168
|
+
expect(result.css).toContain('--surface-lightness: 1');
|
|
169
|
+
});
|
|
143
170
|
});
|