@seyuna/postcss 1.0.0-canary.15 → 1.0.0-canary.16
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 +8 -0
- package/README.md +168 -72
- package/dist/at-rules/index.js +1 -10
- package/dist/config.d.ts +0 -2
- package/dist/functions/color.js +27 -74
- package/dist/plugin.js +0 -1
- package/package.json +2 -1
- package/release.config.mjs +6 -1
- package/src/at-rules/index.ts +1 -10
- package/src/config.ts +0 -3
- package/src/functions/color.ts +45 -98
- package/src/plugin.ts +0 -1
- package/tests/plugin.test.ts +36 -144
- package/dist/at-rules/conditional.d.ts +0 -6
- package/dist/at-rules/conditional.js +0 -29
- package/dist/at-rules/custom-media.d.ts +0 -15
- package/dist/at-rules/custom-media.js +0 -40
- package/dist/at-rules/mixin.d.ts +0 -10
- package/dist/at-rules/mixin.js +0 -37
- package/src/at-rules/conditional.ts +0 -34
- package/src/at-rules/custom-media.ts +0 -50
- package/src/at-rules/mixin.ts +0 -46
package/src/functions/color.ts
CHANGED
|
@@ -1,61 +1,26 @@
|
|
|
1
1
|
import { PluginContext } from "../config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Utility to parse an oklch string and return its parts
|
|
5
|
-
*/
|
|
6
|
-
function parseOklch(color: string) {
|
|
7
|
-
const match = color.match(/oklch\(([^ ]+) ([^ ]+) ([^ /]+)(?: \/ ([^)]+))?\)/);
|
|
8
|
-
if (!match) return null;
|
|
9
|
-
return {
|
|
10
|
-
l: match[1],
|
|
11
|
-
c: match[2],
|
|
12
|
-
h: match[3],
|
|
13
|
-
a: match[4] || "1",
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function sc(
|
|
18
|
-
context: PluginContext,
|
|
19
|
-
name: string,
|
|
20
|
-
alpha?: string,
|
|
21
|
-
lightness?: string,
|
|
22
|
-
chroma?: string
|
|
23
|
-
) {
|
|
24
|
-
let a: string = alpha && alpha !== "null" ? alpha : "1";
|
|
25
|
-
let l: string = lightness && lightness !== "null" ? lightness : "var(--lightness)";
|
|
26
|
-
let c: string = chroma && chroma !== "null" ? chroma : "var(--chroma)";
|
|
27
|
-
let h: string = `var(--${name}-hue)`;
|
|
28
|
-
|
|
29
|
-
return `oklch(${l} ${c} ${h} / ${a})`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function fc(
|
|
33
|
-
context: PluginContext,
|
|
34
|
-
name: string,
|
|
35
|
-
alpha?: string,
|
|
36
|
-
lightness?: string,
|
|
37
|
-
chroma?: string
|
|
38
|
-
) {
|
|
39
|
-
let a: string = alpha && alpha !== "null" ? alpha : "1";
|
|
40
|
-
let l: string = lightness && lightness !== "null" ? lightness : `var(--${name}-lightness)`;
|
|
41
|
-
let c: string = chroma && chroma !== "null" ? chroma : `var(--${name}-chroma)`;
|
|
42
|
-
let h: string = `var(--${name}-hue)`;
|
|
43
|
-
|
|
44
|
-
return `oklch(${l} ${c} ${h} / ${a})`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
3
|
/**
|
|
48
4
|
* Resolves a color name to its CSS variables based on its type (standard or fixed)
|
|
49
5
|
*/
|
|
50
|
-
function getColorVariables(context: PluginContext, color: string) {
|
|
6
|
+
function getColorVariables(context: PluginContext, color: string, type?: 'sc' | 'fc') {
|
|
51
7
|
const { config } = context;
|
|
52
8
|
const hues = config?.ui?.theme?.hues || {};
|
|
53
9
|
const colors = config?.ui?.theme?.colors || {};
|
|
54
10
|
const lightColors = config?.ui?.theme?.light?.colors || {};
|
|
55
11
|
const darkColors = config?.ui?.theme?.dark?.colors || {};
|
|
56
12
|
|
|
57
|
-
|
|
58
|
-
|
|
13
|
+
const isStandard = color in hues;
|
|
14
|
+
const isFixed = color in colors || color in lightColors || color in darkColors;
|
|
15
|
+
|
|
16
|
+
if (type === 'sc' && !isStandard) {
|
|
17
|
+
throw new Error(`Standard color '${color}' not found in seyuna.json hues`);
|
|
18
|
+
}
|
|
19
|
+
if (type === 'fc' && !isFixed) {
|
|
20
|
+
throw new Error(`Fixed color '${color}' not found in seyuna.json colors`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isStandard) {
|
|
59
24
|
return {
|
|
60
25
|
l: "var(--lightness)",
|
|
61
26
|
c: "var(--chroma)",
|
|
@@ -63,8 +28,7 @@ function getColorVariables(context: PluginContext, color: string) {
|
|
|
63
28
|
};
|
|
64
29
|
}
|
|
65
30
|
|
|
66
|
-
|
|
67
|
-
if (color in colors || color in lightColors || color in darkColors) {
|
|
31
|
+
if (isFixed) {
|
|
68
32
|
return {
|
|
69
33
|
l: `var(--${color}-lightness)`,
|
|
70
34
|
c: `var(--${color}-chroma)`,
|
|
@@ -72,72 +36,55 @@ function getColorVariables(context: PluginContext, color: string) {
|
|
|
72
36
|
};
|
|
73
37
|
}
|
|
74
38
|
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
l: `var(--${color}-lightness)`,
|
|
78
|
-
c: `var(--${color}-chroma)`,
|
|
79
|
-
h: `var(--${color}-hue)`,
|
|
80
|
-
};
|
|
39
|
+
throw new Error(`Color '${color}' not found in seyuna.json`);
|
|
81
40
|
}
|
|
82
41
|
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
42
|
+
export function sc(
|
|
43
|
+
context: PluginContext,
|
|
44
|
+
name: string,
|
|
45
|
+
alpha?: string,
|
|
46
|
+
lightness?: string,
|
|
47
|
+
chroma?: string
|
|
48
|
+
) {
|
|
49
|
+
const vars = getColorVariables(context, name, 'sc');
|
|
50
|
+
const a = alpha && alpha !== "null" ? alpha : "1";
|
|
51
|
+
const l = lightness && lightness !== "null" ? lightness : vars.l;
|
|
52
|
+
const c = chroma && chroma !== "null" ? chroma : vars.c;
|
|
88
53
|
|
|
54
|
+
return `oklch(${l} ${c} ${vars.h} / ${a})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function fc(
|
|
58
|
+
context: PluginContext,
|
|
59
|
+
name: string,
|
|
60
|
+
alpha?: string,
|
|
61
|
+
lightness?: string,
|
|
62
|
+
chroma?: string
|
|
63
|
+
) {
|
|
64
|
+
const vars = getColorVariables(context, name, 'fc');
|
|
65
|
+
const a = alpha && alpha !== "null" ? alpha : "1";
|
|
66
|
+
const l = lightness && lightness !== "null" ? lightness : vars.l;
|
|
67
|
+
const c = chroma && chroma !== "null" ? chroma : vars.c;
|
|
68
|
+
|
|
69
|
+
return `oklch(${l} ${c} ${vars.h} / ${a})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function alpha(context: PluginContext, color: string, value: string) {
|
|
89
73
|
const { l, c, h } = getColorVariables(context, color);
|
|
90
74
|
return `oklch(${l} ${c} ${h} / ${value})`;
|
|
91
75
|
}
|
|
92
76
|
|
|
93
77
|
export function lighten(context: PluginContext, color: string, amount: string) {
|
|
94
|
-
const parsed = parseOklch(color);
|
|
95
|
-
|
|
96
|
-
if (parsed) {
|
|
97
|
-
if (parsed.l.startsWith('var(')) {
|
|
98
|
-
return `oklch(calc(${parsed.l} + ${amount}) ${parsed.c} ${parsed.h} / ${parsed.a})`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const lValue = parseFloat(parsed.l);
|
|
102
|
-
const amtValue = parseFloat(amount);
|
|
103
|
-
return `oklch(${Math.min(1, lValue + amtValue)} ${parsed.c} ${parsed.h} / ${parsed.a})`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
78
|
const { l, c, h } = getColorVariables(context, color);
|
|
107
79
|
return `oklch(calc(${l} + ${amount}) ${c} ${h} / 1)`;
|
|
108
80
|
}
|
|
109
81
|
|
|
110
82
|
export function darken(context: PluginContext, color: string, amount: string) {
|
|
111
|
-
const parsed = parseOklch(color);
|
|
112
|
-
|
|
113
|
-
if (parsed) {
|
|
114
|
-
if (parsed.l.startsWith('var(')) {
|
|
115
|
-
return `oklch(calc(${parsed.l} - ${amount}) ${parsed.c} ${parsed.h} / ${parsed.a})`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const lValue = parseFloat(parsed.l);
|
|
119
|
-
const amtValue = parseFloat(amount);
|
|
120
|
-
return `oklch(${Math.max(0, lValue - amtValue)} ${parsed.c} ${parsed.h} / ${parsed.a})`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
83
|
const { l, c, h } = getColorVariables(context, color);
|
|
124
84
|
return `oklch(calc(${l} - ${amount}) ${c} ${h} / 1)`;
|
|
125
85
|
}
|
|
126
86
|
|
|
127
87
|
export function contrast(context: PluginContext, color: string) {
|
|
128
|
-
const
|
|
129
|
-
let l: string;
|
|
130
|
-
|
|
131
|
-
if (parsed) {
|
|
132
|
-
l = parsed.l;
|
|
133
|
-
} else {
|
|
134
|
-
const vars = getColorVariables(context, color);
|
|
135
|
-
l = vars.l;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Dynamic CSS contrast logic:
|
|
139
|
-
// (L - 0.6) * -1000 will be:
|
|
140
|
-
// - very negative if L > 0.6 (clamped to 0 / black)
|
|
141
|
-
// - very positive if L < 0.6 (clamped to 1 / white)
|
|
88
|
+
const { l } = getColorVariables(context, color);
|
|
142
89
|
return `oklch(calc((${l} - 0.6) * -1000) 0 0)`;
|
|
143
90
|
}
|
package/src/plugin.ts
CHANGED
package/tests/plugin.test.ts
CHANGED
|
@@ -9,6 +9,10 @@ const mockConfig = {
|
|
|
9
9
|
primary: "200",
|
|
10
10
|
secondary: "100"
|
|
11
11
|
},
|
|
12
|
+
colors: {
|
|
13
|
+
white: { lightness: 1, chroma: 0, hue: 0 },
|
|
14
|
+
black: { lightness: 0, chroma: 0, hue: 0 }
|
|
15
|
+
},
|
|
12
16
|
light: {
|
|
13
17
|
lightness: 0.66,
|
|
14
18
|
colors: {
|
|
@@ -28,8 +32,9 @@ const mockConfig = {
|
|
|
28
32
|
}
|
|
29
33
|
};
|
|
30
34
|
|
|
31
|
-
async function run(input: string, opts = {}) {
|
|
32
|
-
|
|
35
|
+
async function run(input: string, opts: any = {}) {
|
|
36
|
+
const mergedOpts = { config: mockConfig, ...opts };
|
|
37
|
+
return postcss([plugin(mergedOpts)]).process(input, { from: undefined });
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
describe('Seyuna PostCSS Plugin', () => {
|
|
@@ -47,23 +52,23 @@ describe('Seyuna PostCSS Plugin', () => {
|
|
|
47
52
|
expect(result.css).toContain(output);
|
|
48
53
|
});
|
|
49
54
|
|
|
50
|
-
it('processes alpha() function', async () => {
|
|
51
|
-
const input = '.test { color: alpha(
|
|
52
|
-
const output = 'color: oklch(
|
|
55
|
+
it('processes alpha() function with color name', async () => {
|
|
56
|
+
const input = '.test { color: alpha(primary, 0.5); }';
|
|
57
|
+
const output = 'color: oklch(var(--lightness) var(--chroma) var(--primary-hue) / 0.5)';
|
|
53
58
|
const result = await run(input);
|
|
54
59
|
expect(result.css).toContain(output);
|
|
55
60
|
});
|
|
56
61
|
|
|
57
|
-
it('processes lighten() function', async () => {
|
|
58
|
-
const input = '.test { color: lighten(
|
|
59
|
-
const output = 'color: oklch(
|
|
62
|
+
it('processes lighten() function with color name', async () => {
|
|
63
|
+
const input = '.test { color: lighten(primary, 0.1); }';
|
|
64
|
+
const output = 'color: oklch(calc(var(--lightness) + 0.1) var(--chroma) var(--primary-hue) / 1)';
|
|
60
65
|
const result = await run(input);
|
|
61
66
|
expect(result.css).toContain(output);
|
|
62
67
|
});
|
|
63
68
|
|
|
64
|
-
it('processes
|
|
65
|
-
const input = '.test { color:
|
|
66
|
-
const output = 'color: oklch(calc(var(--
|
|
69
|
+
it('processes darken() function with color name', async () => {
|
|
70
|
+
const input = '.test { color: darken(primary, 0.1); }';
|
|
71
|
+
const output = 'color: oklch(calc(var(--lightness) - 0.1) var(--chroma) var(--primary-hue) / 1)';
|
|
67
72
|
const result = await run(input);
|
|
68
73
|
expect(result.css).toContain(output);
|
|
69
74
|
});
|
|
@@ -90,108 +95,21 @@ describe('Seyuna PostCSS Plugin', () => {
|
|
|
90
95
|
expect(result.css).toContain('[data-mode="light"] &');
|
|
91
96
|
});
|
|
92
97
|
|
|
93
|
-
it('processes
|
|
94
|
-
const input = `
|
|
95
|
-
@define-mixin btn {
|
|
96
|
-
padding: 1rem;
|
|
97
|
-
border-radius: 4px;
|
|
98
|
-
}
|
|
99
|
-
.primary-btn {
|
|
100
|
-
@apply btn;
|
|
101
|
-
color: white;
|
|
102
|
-
}
|
|
103
|
-
`;
|
|
104
|
-
const result = await run(input);
|
|
105
|
-
expect(result.css).toContain('.primary-btn {');
|
|
106
|
-
expect(result.css).toContain('padding: 1rem');
|
|
107
|
-
expect(result.css).toContain('border-radius: 4px');
|
|
108
|
-
expect(result.css).toContain('color: white');
|
|
109
|
-
expect(result.css).not.toContain('@define-mixin');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('processes @if directive with literal', async () => {
|
|
113
|
-
const input = `
|
|
114
|
-
.test {
|
|
115
|
-
@if (true) {
|
|
116
|
-
color: red;
|
|
117
|
-
}
|
|
118
|
-
@if (false) {
|
|
119
|
-
color: blue;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
`;
|
|
123
|
-
const result = await run(input);
|
|
124
|
-
expect(result.css).toContain('color: red');
|
|
125
|
-
expect(result.css).not.toContain('color: blue');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('processes @if directive with theme()', async () => {
|
|
129
|
-
const input = `
|
|
130
|
-
.glass {
|
|
131
|
-
@if (theme(ui.features.glass)) {
|
|
132
|
-
backdrop-filter: blur(10px);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
`;
|
|
136
|
-
const config = {
|
|
137
|
-
ui: {
|
|
138
|
-
features: {
|
|
139
|
-
glass: "true"
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const result = await run(input, { config });
|
|
144
|
-
expect(result.css).toContain('backdrop-filter: blur(10px)');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('resolves custom media tokens', async () => {
|
|
148
|
-
const input = `
|
|
149
|
-
@media (--tablet) {
|
|
150
|
-
.sidebar { display: block; }
|
|
151
|
-
}
|
|
152
|
-
`;
|
|
153
|
-
const config = {
|
|
154
|
-
media: {
|
|
155
|
-
tablet: "(min-width: 768px)"
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const result = await run(input, { config });
|
|
159
|
-
expect(result.css).toContain('@media (min-width: 768px)');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('handles @custom-media directive', async () => {
|
|
163
|
-
const input = `
|
|
164
|
-
@custom-media --mobile (max-width: 480px);
|
|
165
|
-
@media (--mobile) {
|
|
166
|
-
.full-width { width: 100%; }
|
|
167
|
-
}
|
|
168
|
-
`;
|
|
169
|
-
const result = await run(input);
|
|
170
|
-
expect(result.css).toContain('@media (max-width: 480px)');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('processes alpha() with color name', async () => {
|
|
98
|
+
it('processes alpha() with standard color', async () => {
|
|
174
99
|
const input = '.test { color: alpha(primary, 0.5); }';
|
|
175
|
-
const output = 'color: oklch(var(--
|
|
176
|
-
const result = await run(input);
|
|
177
|
-
expect(result.css).toContain(output);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('processes lighten() with color name', async () => {
|
|
181
|
-
const input = '.test { color: lighten(primary, 0.1); }';
|
|
182
|
-
const output = 'color: oklch(calc(var(--primary-lightness) + 0.1) var(--primary-chroma) var(--primary-hue) / 1)';
|
|
183
|
-
const result = await run(input);
|
|
100
|
+
const output = 'color: oklch(var(--lightness) var(--chroma) var(--primary-hue) / 0.5)';
|
|
101
|
+
const result = await run(input, { config: mockConfig });
|
|
184
102
|
expect(result.css).toContain(output);
|
|
185
103
|
});
|
|
186
104
|
|
|
187
|
-
it('processes
|
|
188
|
-
const input = '.test { color:
|
|
189
|
-
const output = 'color: oklch(
|
|
190
|
-
const result = await run(input);
|
|
105
|
+
it('processes alpha() with fixed color', async () => {
|
|
106
|
+
const input = '.test { color: alpha(surface, 0.5); }';
|
|
107
|
+
const output = 'color: oklch(var(--surface-lightness) var(--surface-chroma) var(--surface-hue) / 0.5)';
|
|
108
|
+
const result = await run(input, { config: mockConfig });
|
|
191
109
|
expect(result.css).toContain(output);
|
|
192
110
|
});
|
|
193
111
|
|
|
194
|
-
it('processes contrast() with color
|
|
112
|
+
it('processes contrast() with fixed color', async () => {
|
|
195
113
|
const input = '.test { color: contrast(surface); }';
|
|
196
114
|
const output = 'color: oklch(calc((var(--surface-lightness) - 0.6) * -1000) 0 0)';
|
|
197
115
|
const result = await run(input, { config: mockConfig });
|
|
@@ -205,47 +123,21 @@ describe('Seyuna PostCSS Plugin', () => {
|
|
|
205
123
|
expect(result.css).toContain(output);
|
|
206
124
|
});
|
|
207
125
|
|
|
208
|
-
it('
|
|
209
|
-
const input = '.test { color:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const result = await run(input, { config: mockConfig });
|
|
213
|
-
expect(result.css).toContain(output);
|
|
126
|
+
it('throws error for unknown standard color in strict mode', async () => {
|
|
127
|
+
const input = '.test { color: sc(unknown); }';
|
|
128
|
+
await expect(run(input, { config: mockConfig, strict: true }))
|
|
129
|
+
.rejects.toThrow(/Standard color 'unknown' not found/);
|
|
214
130
|
});
|
|
215
131
|
|
|
216
|
-
it('
|
|
217
|
-
const input = '.test { color:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const result = await run(input, { config: mockConfig });
|
|
221
|
-
expect(result.css).toContain(output);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('processes nested functions: contrast(fc(white))', async () => {
|
|
225
|
-
const input = '.test { color: contrast(fc(white)); }';
|
|
226
|
-
// fc(white) -> oklch(var(--white-lightness) ...)
|
|
227
|
-
// contrast extracts lightness -> var(--white-lightness)
|
|
228
|
-
const output = 'color: oklch(calc((var(--white-lightness) - 0.6) * -1000) 0 0)';
|
|
229
|
-
const result = await run(input, { config: mockConfig });
|
|
230
|
-
expect(result.css).toContain(output);
|
|
132
|
+
it('throws error for unknown fixed color in strict mode', async () => {
|
|
133
|
+
const input = '.test { color: fc(unknown); }';
|
|
134
|
+
await expect(run(input, { config: mockConfig, strict: true }))
|
|
135
|
+
.rejects.toThrow(/Fixed color 'unknown' not found/);
|
|
231
136
|
});
|
|
232
137
|
|
|
233
|
-
it('
|
|
234
|
-
const input = '.test { color: alpha(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const output = 'color: oklch(var(--lightness) var(--chroma) var(--primary-hue) / 0.5)';
|
|
238
|
-
const result = await run(input, { config: mockConfig });
|
|
239
|
-
expect(result.css).toContain(output);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('processes multiple nested functions: contrast(alpha(fc(surface), 1))', async () => {
|
|
243
|
-
const input = '.test { color: contrast(alpha(fc(surface), 1)); }';
|
|
244
|
-
// inner-most: fc(surface) -> oklch(var(--surface-lightness) ...)
|
|
245
|
-
// next: alpha(..., 1) -> oklch(var(--surface-lightness) ...)
|
|
246
|
-
// outer: contrast extracts lightness -> var(--surface-lightness)
|
|
247
|
-
const output = 'color: oklch(calc((var(--surface-lightness) - 0.6) * -1000) 0 0)';
|
|
248
|
-
const result = await run(input, { config: mockConfig });
|
|
249
|
-
expect(result.css).toContain(output);
|
|
138
|
+
it('throws error for unknown color in alpha() in strict mode', async () => {
|
|
139
|
+
const input = '.test { color: alpha(unknown, 0.5); }';
|
|
140
|
+
await expect(run(input, { config: mockConfig, strict: true }))
|
|
141
|
+
.rejects.toThrow(/Color 'unknown' not found in seyuna.json/);
|
|
250
142
|
});
|
|
251
143
|
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { processFunctions } from "../parser";
|
|
2
|
-
/**
|
|
3
|
-
* Handler for @if (condition) { ... }
|
|
4
|
-
*/
|
|
5
|
-
export function conditional(atRule, context) {
|
|
6
|
-
const { functions: fnMap } = context;
|
|
7
|
-
// Clean up params (remove parentheses if present)
|
|
8
|
-
let condition = atRule.params.trim().replace(/^\(|\)$/g, '').trim();
|
|
9
|
-
// Process functions in the condition (e.g., theme access)
|
|
10
|
-
const evaluated = processFunctions(condition, fnMap, atRule, context).trim();
|
|
11
|
-
// Simple truthy evaluation
|
|
12
|
-
const isFalsy = !evaluated ||
|
|
13
|
-
evaluated === 'false' ||
|
|
14
|
-
evaluated === '0' ||
|
|
15
|
-
evaluated === 'null' ||
|
|
16
|
-
evaluated === 'undefined';
|
|
17
|
-
if (!isFalsy) {
|
|
18
|
-
// Ungroup the nodes
|
|
19
|
-
if (atRule.nodes && atRule.nodes.length > 0) {
|
|
20
|
-
atRule.replaceWith(...atRule.nodes.map(n => n.clone()));
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
atRule.remove();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
atRule.remove();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { AtRule } from "postcss";
|
|
2
|
-
import { PluginContext } from "../config";
|
|
3
|
-
/**
|
|
4
|
-
* Global store for CSS-defined custom media (if needed)
|
|
5
|
-
* However, we primarily use the config.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Handler for @media at-rules to resolve custom media tokens
|
|
9
|
-
*/
|
|
10
|
-
export declare function resolveCustomMedia(atRule: AtRule, context: PluginContext): void;
|
|
11
|
-
/**
|
|
12
|
-
* [Future-proofing] Handler for @custom-media --name query
|
|
13
|
-
* Adds to the config-like store for the current run
|
|
14
|
-
*/
|
|
15
|
-
export declare function defineCustomMedia(atRule: AtRule, context: PluginContext): void;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global store for CSS-defined custom media (if needed)
|
|
3
|
-
* However, we primarily use the config.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Handler for @media at-rules to resolve custom media tokens
|
|
7
|
-
*/
|
|
8
|
-
export function resolveCustomMedia(atRule, context) {
|
|
9
|
-
const { config } = context;
|
|
10
|
-
const mediaTokens = config.media || {};
|
|
11
|
-
// Regex to find --tokens anywhere
|
|
12
|
-
let params = atRule.params;
|
|
13
|
-
const tokenRegex = /--[a-zA-Z0-9\-_]+/g;
|
|
14
|
-
params = params.replace(tokenRegex, (match) => {
|
|
15
|
-
const tokenName = match.slice(2);
|
|
16
|
-
if (mediaTokens[tokenName]) {
|
|
17
|
-
return mediaTokens[tokenName];
|
|
18
|
-
}
|
|
19
|
-
return match;
|
|
20
|
-
});
|
|
21
|
-
// Clean up double parentheses like ((...)) if any
|
|
22
|
-
params = params.replace(/\(\((.+)\)\)/g, '($1)');
|
|
23
|
-
atRule.params = params;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* [Future-proofing] Handler for @custom-media --name query
|
|
27
|
-
* Adds to the config-like store for the current run
|
|
28
|
-
*/
|
|
29
|
-
export function defineCustomMedia(atRule, context) {
|
|
30
|
-
const match = atRule.params.match(/^(--[a-zA-Z0-9\-_]+)\s+(.+)$/);
|
|
31
|
-
if (match) {
|
|
32
|
-
const name = match[1].slice(2);
|
|
33
|
-
const query = match[2];
|
|
34
|
-
if (!context.config.media) {
|
|
35
|
-
context.config.media = {};
|
|
36
|
-
}
|
|
37
|
-
context.config.media[name] = query;
|
|
38
|
-
}
|
|
39
|
-
atRule.remove();
|
|
40
|
-
}
|
package/dist/at-rules/mixin.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { AtRule } from "postcss";
|
|
2
|
-
import { PluginContext } from "../config";
|
|
3
|
-
/**
|
|
4
|
-
* Handler for @define-mixin [name] { ... }
|
|
5
|
-
*/
|
|
6
|
-
export declare function defineMixin(atRule: AtRule, context: PluginContext): void;
|
|
7
|
-
/**
|
|
8
|
-
* Handler for @apply [name]
|
|
9
|
-
*/
|
|
10
|
-
export declare function applyMixin(atRule: AtRule, context: PluginContext): void;
|
package/dist/at-rules/mixin.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { reportError } from "../errors";
|
|
2
|
-
/**
|
|
3
|
-
* Handler for @define-mixin [name] { ... }
|
|
4
|
-
*/
|
|
5
|
-
export function defineMixin(atRule, context) {
|
|
6
|
-
const name = atRule.params.trim();
|
|
7
|
-
if (!name) {
|
|
8
|
-
reportError("Mixin name is required", atRule, context);
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
// Store the nodes (cloned)
|
|
12
|
-
context.mixins[name] = atRule.nodes?.map(n => n.clone()) || [];
|
|
13
|
-
// Remove the at-rule from the output
|
|
14
|
-
atRule.remove();
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Handler for @apply [name]
|
|
18
|
-
*/
|
|
19
|
-
export function applyMixin(atRule, context) {
|
|
20
|
-
const name = atRule.params.trim();
|
|
21
|
-
if (!name) {
|
|
22
|
-
reportError("Mixin name is required for @apply", atRule, context);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const mixinNodes = context.mixins[name];
|
|
26
|
-
if (!mixinNodes) {
|
|
27
|
-
reportError(`Mixin "${name}" not found`, atRule, context);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// Inject the nodes, ensuring they have the correct source for mapping
|
|
31
|
-
const nodesToInject = mixinNodes.map(n => {
|
|
32
|
-
const cloned = n.clone();
|
|
33
|
-
cloned.source = atRule.source;
|
|
34
|
-
return cloned;
|
|
35
|
-
});
|
|
36
|
-
atRule.replaceWith(...nodesToInject);
|
|
37
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { AtRule } from "postcss";
|
|
2
|
-
import { PluginContext } from "../config";
|
|
3
|
-
import { processFunctions } from "../parser";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Handler for @if (condition) { ... }
|
|
7
|
-
*/
|
|
8
|
-
export function conditional(atRule: AtRule, context: PluginContext) {
|
|
9
|
-
const { functions: fnMap } = context;
|
|
10
|
-
|
|
11
|
-
// Clean up params (remove parentheses if present)
|
|
12
|
-
let condition = atRule.params.trim().replace(/^\(|\)$/g, '').trim();
|
|
13
|
-
|
|
14
|
-
// Process functions in the condition (e.g., theme access)
|
|
15
|
-
const evaluated = processFunctions(condition, fnMap, atRule, context).trim();
|
|
16
|
-
|
|
17
|
-
// Simple truthy evaluation
|
|
18
|
-
const isFalsy = !evaluated ||
|
|
19
|
-
evaluated === 'false' ||
|
|
20
|
-
evaluated === '0' ||
|
|
21
|
-
evaluated === 'null' ||
|
|
22
|
-
evaluated === 'undefined';
|
|
23
|
-
|
|
24
|
-
if (!isFalsy) {
|
|
25
|
-
// Ungroup the nodes
|
|
26
|
-
if (atRule.nodes && atRule.nodes.length > 0) {
|
|
27
|
-
atRule.replaceWith(...atRule.nodes.map(n => n.clone()));
|
|
28
|
-
} else {
|
|
29
|
-
atRule.remove();
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
atRule.remove();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { AtRule } from "postcss";
|
|
2
|
-
import { PluginContext } from "../config";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Global store for CSS-defined custom media (if needed)
|
|
6
|
-
* However, we primarily use the config.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Handler for @media at-rules to resolve custom media tokens
|
|
11
|
-
*/
|
|
12
|
-
export function resolveCustomMedia(atRule: AtRule, context: PluginContext) {
|
|
13
|
-
const { config } = context;
|
|
14
|
-
const mediaTokens = (config as any).media || {};
|
|
15
|
-
|
|
16
|
-
// Regex to find --tokens anywhere
|
|
17
|
-
let params = atRule.params;
|
|
18
|
-
const tokenRegex = /--[a-zA-Z0-9\-_]+/g;
|
|
19
|
-
|
|
20
|
-
params = params.replace(tokenRegex, (match) => {
|
|
21
|
-
const tokenName = match.slice(2);
|
|
22
|
-
if (mediaTokens[tokenName]) {
|
|
23
|
-
return mediaTokens[tokenName];
|
|
24
|
-
}
|
|
25
|
-
return match;
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Clean up double parentheses like ((...)) if any
|
|
29
|
-
params = params.replace(/\(\((.+)\)\)/g, '($1)');
|
|
30
|
-
|
|
31
|
-
atRule.params = params;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* [Future-proofing] Handler for @custom-media --name query
|
|
36
|
-
* Adds to the config-like store for the current run
|
|
37
|
-
*/
|
|
38
|
-
export function defineCustomMedia(atRule: AtRule, context: PluginContext) {
|
|
39
|
-
const match = atRule.params.match(/^(--[a-zA-Z0-9\-_]+)\s+(.+)$/);
|
|
40
|
-
if (match) {
|
|
41
|
-
const name = match[1].slice(2);
|
|
42
|
-
const query = match[2];
|
|
43
|
-
|
|
44
|
-
if (!(context.config as any).media) {
|
|
45
|
-
(context.config as any).media = {};
|
|
46
|
-
}
|
|
47
|
-
(context.config as any).media[name] = query;
|
|
48
|
-
}
|
|
49
|
-
atRule.remove();
|
|
50
|
-
}
|