Skip to content

Commit 8b3f5ab

Browse files
authored
fix: selector and atrules nesting (#14)
1 parent cf10d8a commit 8b3f5ab

18 files changed

+332
-136
lines changed

.changeset/tidy-comics-shave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hebilicious/cssforge": patch
3+
---
4+
5+
fix nesting for selector and atRules

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
- ab752be: # Introduce variantNameOnly feature for themes.
88

99
When working with themes, you can choose to only include the variant name in the CSS
10-
variable name by setting `variantNameOnly: true` in the color definition settings. This is
11-
usually used in combination with `condition` to conditionnally apply themes.
10+
variable name by setting `variantNameOnly: true` in the color definition settings. This
11+
is usually used in combination with `condition` to conditionnally apply themes.
1212

1313
- Default: `--theme-${themeName}-${colorName}-${variantName}`
1414
- VariantOnly Name: `--${variantName}`

README.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export default defineConfig({
217217
cyan: { hex: "#00FFFF" },
218218
},
219219
settings: {
220-
condition: ".Another",
220+
selector: ".Another",
221221
},
222222
},
223223
},
@@ -272,7 +272,7 @@ export default defineConfig({
272272
},
273273
},
274274
settings: {
275-
condition: "@media (prefers-color-scheme: dark)",
275+
atRule: "@media (prefers-color-scheme: dark)",
276276
},
277277
},
278278
pink: {
@@ -292,7 +292,7 @@ export default defineConfig({
292292
},
293293
},
294294
settings: {
295-
condition: ".ThemePink",
295+
selector: ".ThemePink",
296296
},
297297
},
298298
},
@@ -321,7 +321,7 @@ export default defineConfig({
321321
cyan: { hex: "#00FFFF" },
322322
},
323323
settings: {
324-
condition: ".Another",
324+
selector: ".Another",
325325
},
326326
},
327327
},
@@ -376,7 +376,7 @@ export default defineConfig({
376376
},
377377
},
378378
settings: {
379-
condition: "@media (prefers-color-scheme: dark)",
379+
atRule: "@media (prefers-color-scheme: dark)",
380380
},
381381
},
382382
pink: {
@@ -396,7 +396,7 @@ export default defineConfig({
396396
},
397397
},
398398
settings: {
399-
condition: ".ThemePink",
399+
selector: ".ThemePink",
400400
},
401401
},
402402
},
@@ -418,11 +418,6 @@ This will generate the following CSS :
418418
--palette-simple-blue: oklch(45.201% 0.31321 264.05202);
419419
--palette-simple-violet: oklch(70% 0.2 270);
420420
--palette-simple-red: oklch(62.796% 0.25768 29.23388);
421-
/* another */
422-
.Another {
423-
--palette-another-yellow: oklch(96.798% 0.21101 109.76924);
424-
--palette-another-cyan: oklch(90.54% 0.15455 194.76896);
425-
}
426421
/* Gradients */
427422
/* white-green */
428423
--gradients-white-green-primary: linear-gradient(
@@ -441,28 +436,33 @@ This will generate the following CSS :
441436
--primary: var(--palette-another-yellow);
442437
--secondary: var(--palette-another-cyan);
443438
}
444-
/* Theme: pink */
445-
.ThemePink {
446-
/* background */
447-
--primary: var(--palette-simple-red);
448-
--secondary: var(--palette-simple-violet);
449-
}
439+
}
440+
/* another */
441+
.Another {
442+
--palette-another-yellow: oklch(96.798% 0.21101 109.76924);
443+
--palette-another-cyan: oklch(90.54% 0.15455 194.76896);
444+
}
445+
/* Theme: pink */
446+
.ThemePink {
447+
/* background */
448+
--primary: var(--palette-simple-red);
449+
--secondary: var(--palette-simple-violet);
450450
}
451451
```
452452

453453
<!-- /md:generate -->
454454

455455
#### Condition
456456

457-
You can conditionnally apply colors, gradients or themes by setting the `condition`
458-
property to a selector or media query. Your variables will be wrapped within the
459-
condition.
457+
You can conditionnally apply colors, gradients or themes by setting the `atRule` or the
458+
`selector` properties. Your variables will be wrapped within `:root` and the selectors
459+
will be placed outside of it.
460460

461461
#### Theme: Variant Name Only
462462

463463
When working with themes, you can choose to only include the variant name in the CSS
464464
variable name by setting `variantNameOnly: true` in the color definition settings. This is
465-
usually used in combination with `condition` to conditionnally apply themes.
465+
usually used in combination with `selector` to conditionnally apply themes.
466466

467467
- Default: `--theme-${themeName}-${colorName}-${variantName}`
468468
- VariantOnly Name: `--${variantName}`

example/basic/cssforge.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,29 +60,29 @@ export default defineConfig(
6060
100: { hex: "#4F46E5" },
6161
},
6262
},
63-
// dark-mode overrides wrapped in a condition
63+
// dark-mode overrides wrapped in a atRule
6464
coral_dark: {
6565
value: {
6666
100: { hex: "#FF6347" },
6767
},
6868
settings: {
69-
condition: "@media (prefers-color-scheme: dark)",
69+
atRule: "@media (prefers-color-scheme: dark)",
7070
},
7171
},
7272
mint_dark: {
7373
value: {
7474
100: { hex: "#22C55E" },
7575
},
7676
settings: {
77-
condition: "@media (prefers-color-scheme: dark)",
77+
atRule: "@media (prefers-color-scheme: dark)",
7878
},
7979
},
8080
indigo_dark: {
8181
value: {
8282
100: { hex: "#4338CA" },
8383
},
8484
settings: {
85-
condition: "@media (prefers-color-scheme: dark)",
85+
atRule: "@media (prefers-color-scheme: dark)",
8686
},
8787
},
8888
},

src/generator.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,34 +143,52 @@ export function generateTS(config: Partial<CSSForgeConfig>): string {
143143
*/
144144
export function generateCSS(config: Partial<CSSForgeConfig>): string {
145145
const chunks: string[] = ["/*____ CSSForge ____*/", ":root {"];
146+
const outsideChunks: string[] = [];
146147
const processedConfig: {
147-
[key: string]: { css: string; resolveMap: ResolveMap } | undefined;
148+
[key: string]:
149+
| { css: { root?: string; outside?: string }; resolveMap: ResolveMap }
150+
| undefined;
148151
} = {};
149152

150153
// Process colors if present
151154
if (config.colors) {
152155
processedConfig.colors = processColors(config.colors);
153156
if (processedConfig.colors) {
154-
chunks.push("/*____ Colors ____*/");
155-
chunks.push(processedConfig.colors.css);
157+
if (processedConfig.colors.css.root) {
158+
chunks.push("/*____ Colors ____*/");
159+
chunks.push(processedConfig.colors.css.root);
160+
}
161+
if (processedConfig.colors.css.outside) {
162+
outsideChunks.push(processedConfig.colors.css.outside);
163+
}
156164
}
157165
}
158166

159167
// Process spacing if present
160168
if (config.spacing) {
161169
processedConfig.spacing = processSpacing(config.spacing);
162170
if (processedConfig.spacing) {
163-
chunks.push("/*____ Spacing ____*/");
164-
chunks.push(processedConfig.spacing.css);
171+
if (processedConfig.spacing.css.root) {
172+
chunks.push("/*____ Spacing ____*/");
173+
chunks.push(processedConfig.spacing.css.root);
174+
}
175+
if (processedConfig.spacing.css.outside) {
176+
outsideChunks.push(processedConfig.spacing.css.outside);
177+
}
165178
}
166179
}
167180

168181
// Process Typography if present
169182
if (config.typography) {
170183
processedConfig.typography = processTypography(config.typography);
171184
if (processedConfig.typography) {
172-
chunks.push("/*____ Typography ____*/");
173-
chunks.push(processedConfig.typography.css);
185+
if (processedConfig.typography.css.root) {
186+
chunks.push("/*____ Typography ____*/");
187+
chunks.push(processedConfig.typography.css.root);
188+
}
189+
if (processedConfig.typography.css.outside) {
190+
outsideChunks.push(processedConfig.typography.css.outside);
191+
}
174192
}
175193
}
176194

@@ -182,12 +200,19 @@ export function generateCSS(config: Partial<CSSForgeConfig>): string {
182200
spacing: config.spacing,
183201
});
184202
if (primitiveVars) {
185-
chunks.push("/*____ Primitives ____*/");
186-
chunks.push(primitiveVars.css);
203+
if (primitiveVars.css.root) {
204+
chunks.push("/*____ Primitives ____*/");
205+
chunks.push(primitiveVars.css.root);
206+
}
207+
if (primitiveVars.css.outside) {
208+
outsideChunks.push(primitiveVars.css.outside);
209+
}
187210
}
188211
}
189212

190213
chunks.push("}");
191-
// Join all chunks with double newline for readability
214+
215+
if (outsideChunks.length > 0) chunks.push(outsideChunks.join("\n"));
216+
192217
return chunks.join("\n");
193218
}

src/lib.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export type ResolveMap = Map<
1212
* CSS and a resolve map.
1313
*/
1414
export interface Output {
15-
/** The generated CSS string. */
16-
css: string;
15+
/** The generated CSS strings. */
16+
css: { root?: string; outside?: string };
1717
/** A map for resolving variable paths. */
1818
resolveMap: ResolveMap;
1919
}

src/modules/colors.ts

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ interface ColorVariants {
3131

3232
export interface WithCondition {
3333
/**
34-
* CSS condition to wrap variables in (e.g., ".MyClass", "@media (prefers-color-scheme: dark)")
34+
* CSS selector to wrap variables in (e.g., ".MyClass"). This will be extracted out of the root.
3535
*/
36-
condition?: string;
36+
selector?: string;
37+
/**
38+
* CSS at-rule to wrap variables in (e.g., "@supports (display: grid)")
39+
*/
40+
atRule?: string;
3741
}
3842
/**
3943
* Settings for palette colors, including optional conditions like media queries.
@@ -219,41 +223,65 @@ function colorValueToOklch(value: ColorValueOrString): string {
219223
* ```
220224
*/
221225
export function processColors(colors: ColorConfig): Output {
222-
const cssOutput: string[] = [];
226+
const rootOutput: string[] = [];
227+
const outsideOutput: string[] = [];
223228
const resolveMap: ResolveMap = new Map();
224-
cssOutput.push(`/* Palette */`);
229+
rootOutput.push(`/* Palette */`);
225230
const moduleKey = "palette";
226231

227232
function conditionalBuilder(
228233
settings: WithCondition | undefined,
229234
initialComment: string,
230235
) {
231-
const leadingComments: string[] = [];
232236
const innerComments: string[] = [];
233237
const vars: string[] = [];
234238

235-
if (settings?.condition) {
236-
leadingComments.push(initialComment);
237-
} else {
238-
cssOutput.push(initialComment);
239-
}
239+
const hasSelector = Boolean(settings?.selector);
240+
const hasAtRule = Boolean(settings?.atRule);
241+
242+
// If no settings provided, emit comment immediately into root output
243+
if (!hasSelector && !hasAtRule) rootOutput.push(initialComment);
240244

241245
return {
242246
addComment(c: string) {
243-
if (settings?.condition) innerComments.push(c);
244-
else cssOutput.push(c);
247+
if (hasSelector || hasAtRule) innerComments.push(c);
248+
else rootOutput.push(c);
245249
},
246250
pushVariable(v: string) {
247-
if (settings?.condition) vars.push(v);
248-
else cssOutput.push(v);
251+
if (hasSelector || hasAtRule) vars.push(v);
252+
else rootOutput.push(v);
249253
},
250254
finalize() {
251-
if (settings?.condition && vars.length > 0) {
252-
cssOutput.push(...leadingComments);
253-
cssOutput.push(`${settings.condition} {`);
254-
cssOutput.push(...innerComments.map((c) => ` ${c}`));
255-
cssOutput.push(...vars.map((v) => ` ${v}`));
256-
cssOutput.push(`}`);
255+
if (!hasSelector && !hasAtRule) return;
256+
if (vars.length === 0 && innerComments.length === 0) return;
257+
if (!settings) return;
258+
if (hasSelector && hasAtRule) {
259+
outsideOutput.push(initialComment);
260+
outsideOutput.push(`${settings.atRule} {`);
261+
outsideOutput.push(` ${settings.selector} {`);
262+
outsideOutput.push(...innerComments.map((c) => ` ${c}`));
263+
outsideOutput.push(...vars.map((v) => ` ${v}`));
264+
outsideOutput.push(` }`);
265+
outsideOutput.push(`}`);
266+
return;
267+
}
268+
269+
if (hasSelector) {
270+
outsideOutput.push(initialComment);
271+
outsideOutput.push(`${settings.selector} {`);
272+
outsideOutput.push(...innerComments.map((c) => ` ${c}`));
273+
outsideOutput.push(...vars.map((v) => ` ${v}`));
274+
outsideOutput.push(`}`);
275+
return;
276+
}
277+
278+
if (hasAtRule) {
279+
rootOutput.push(initialComment);
280+
rootOutput.push(`${settings.atRule} {`);
281+
rootOutput.push(...innerComments.map((c) => ` ${c}`));
282+
rootOutput.push(...vars.map((v) => ` ${v}`));
283+
rootOutput.push(`}`);
284+
return;
257285
}
258286
},
259287
};
@@ -289,9 +317,12 @@ export function processColors(colors: ColorConfig): Output {
289317
}
290318

291319
if (colors.gradients) {
292-
cssOutput.push(`/* Gradients */`);
320+
rootOutput.push(`/* Gradients */`);
293321
const moduleKey = "gradients";
294-
const palette = { css: cssOutput.join("\n"), resolveMap };
322+
const palette = {
323+
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
324+
resolveMap,
325+
};
295326

296327
for (const [gradientName, gradient] of Object.entries(colors.gradients.value)) {
297328
validateName(gradientName);
@@ -335,9 +366,12 @@ export function processColors(colors: ColorConfig): Output {
335366
}
336367

337368
if (colors.theme) {
338-
cssOutput.push(`/* Themes */`);
369+
rootOutput.push(`/* Themes */`);
339370
const moduleKey = "theme";
340-
const palette = { css: cssOutput.join("\n"), resolveMap };
371+
const palette = {
372+
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
373+
resolveMap,
374+
};
341375

342376
for (const [themeName, themeConfig] of Object.entries(colors.theme)) {
343377
validateName(themeName);
@@ -387,7 +421,9 @@ export function processColors(colors: ColorConfig): Output {
387421
}
388422
}
389423

390-
const output = { css: cssOutput.join("\n"), resolveMap };
391-
// console.log(output);
424+
const output = {
425+
css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") },
426+
resolveMap,
427+
};
392428
return output;
393429
}

0 commit comments

Comments
 (0)