Skip to content

Commit 3bcd04e

Browse files
authored
Merge pull request #414 (css)
feat(css)!: drop processCSSRuleChain and improve processCSSSelectors
2 parents ee91bc7 + 38254bc commit 3bcd04e

File tree

4 files changed

+92
-140
lines changed

4 files changed

+92
-140
lines changed

packages/@poupe-css/README.md

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -464,90 +464,63 @@ expandSelectorAlias('print', customAliases); // '@media print'
464464
expandSelectorAlias('.my-class'); // '.my-class'
465465
```
466466

467-
#### `processCSSRuleChain(selectors: string[], addStarVariants?: boolean): string | string[] | undefined`
467+
#### `processCSSSelectors(selectors: string | string[], options?: ProcessCSSSelectorOptions): string[] | undefined`
468468

469-
Processes an array of CSS selectors and at-rules, merging consecutive
470-
selectors with OR and adding * variants, while keeping at-rules stacked
471-
separately. Returns `undefined` if no valid selectors are found.
472-
473-
```typescript
474-
import { processCSSRuleChain } from '@poupe/css';
475-
476-
// Merge consecutive selectors with star variants
477-
processCSSRuleChain(['.dark', '.custom']);
478-
// Result: '.dark, .dark *, .custom, .custom *'
479-
480-
// At-rules are kept separate
481-
processCSSRuleChain([
482-
'.dark',
483-
'@media (max-width: 768px)',
484-
'.mobile'
485-
]);
486-
// Result: [
487-
// '.dark, .dark *',
488-
// '@media (max-width: 768px)',
489-
// '.mobile, .mobile *'
490-
// ]
491-
492-
// Disable star variants
493-
processCSSRuleChain(['.test1', '.test2'], false);
494-
// Result: '.test1, .test2'
495-
496-
// Uses alias expansion
497-
processCSSRuleChain(['.test', 'media']);
498-
// Result: [
499-
// '.test, .test *',
500-
// '@media (prefers-color-scheme: dark)'
501-
// ]
502-
503-
// Returns undefined for empty arrays
504-
processCSSRuleChain([]); // undefined
505-
```
506-
507-
#### `processCSSSelectors(selectors: string | string[], options?: ProcessCSSSelectorOptions): string | string[] | undefined`
508-
509-
Generic function to process CSS selector arrays with support for alias
510-
expansion and various formatting options. Returns `undefined` if no valid
511-
selectors are found.
469+
Processes CSS selectors and at-rules, handling both strings and arrays.
470+
Merges consecutive selectors with OR and adds * variants, while keeping
471+
at-rules stacked separately. Returns `undefined` if no valid selectors are
472+
found.
512473

513474
```typescript
514475
import { processCSSSelectors } from '@poupe/css';
515476

516477
// Single string selector
517478
processCSSSelectors('.test');
518-
// Result: '.test, .test *'
479+
// Result: ['.test, .test *']
519480

520481
// Array of selectors
521482
processCSSSelectors(['.dark', '.custom']);
522-
// Result: '.dark, .dark *, .custom, .custom *'
483+
// Result: ['.dark, .dark *, .custom, .custom *']
523484

524485
// Comma-separated selectors pass through (when allowCommaPassthrough is true)
525486
processCSSSelectors('.test, .other');
526-
// Result: '.test, .other'
487+
// Result: ['.test, .other']
527488

528489
// Disable star variants
529490
processCSSSelectors(['.test1', '.test2'], { addStarVariants: false });
530-
// Result: '.test1, .test2'
491+
// Result: ['.test1, .test2']
531492

532493
// Use custom aliases
533494
const customAliases = { 'custom': '@media (min-width: 1200px)' };
534495
processCSSSelectors('custom', { aliases: customAliases });
535-
// Result: '@media (min-width: 1200px), @media (min-width: 1200px) *'
496+
// Result: ['@media (min-width: 1200px), @media (min-width: 1200px) *']
536497

537498
// Mixed selectors and aliases
538499
processCSSSelectors(['.test', 'mobile'], { addStarVariants: false });
539500
// Result: ['.test', '@media (max-width: 768px)']
540501

502+
// At-rules are kept separate
503+
processCSSSelectors([
504+
'.dark',
505+
'@media (max-width: 768px)',
506+
'.mobile'
507+
]);
508+
// Result: [
509+
// '.dark, .dark *',
510+
// '@media (max-width: 768px)',
511+
// '.mobile, .mobile *'
512+
// ]
513+
541514
// Returns undefined for empty arrays
542515
processCSSSelectors([]); // undefined
543516

544517
// Alias expansion with single string
545518
processCSSSelectors('media');
546-
// Result: '@media (prefers-color-scheme: dark), @media (prefers-color-scheme: dark) *'
519+
// Result: ['@media (prefers-color-scheme: dark), @media (prefers-color-scheme: dark) *']
547520

548-
// Disable comma passthrough
521+
// Disable comma pass-through
549522
processCSSSelectors('.test, .other', { allowCommaPassthrough: false });
550-
// Result: '.test, .other, .test, .other *'
523+
// Result: ['.test, .other, .test, .other *']
551524
```
552525

553526
### Utility Functions

packages/@poupe-css/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@poupe/css",
3-
"version": "0.2.5",
3+
"version": "0.3.0",
44
"type": "module",
55
"description": "A TypeScript utility library for CSS property manipulation, formatting, and CSS-in-JS operations",
66
"author": "Alejandro Mery <amery@apptly.co>",

packages/@poupe-css/src/__tests__/selectors.test.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { describe, it, expect } from 'vitest';
22

33
import {
4-
processCSSRuleChain,
54
processCSSSelectors,
65
expandSelectorAlias,
76
} from '../selectors';
@@ -42,14 +41,32 @@ describe('expandSelectorAlias', () => {
4241
});
4342
});
4443

45-
describe('processCSSRuleChain', () => {
44+
describe('processCSSSelectors', () => {
45+
it('should handle single string selectors', () => {
46+
expect(processCSSSelectors('.test')).toEqual(['.test, .test *']);
47+
});
48+
49+
it('should handle single string with commas when allowCommaPassthrough is true', () => {
50+
expect(processCSSSelectors('.test, .other')).toEqual(['.test, .other']);
51+
});
52+
53+
it('should add star variants when allowCommaPassthrough is false', () => {
54+
expect(processCSSSelectors('.test, .other', { allowCommaPassthrough: false }))
55+
.toEqual(['.test, .other, .test, .other *']);
56+
});
57+
58+
it('should handle arrays with consecutive selectors', () => {
59+
const result = processCSSSelectors(['.test1', '.test2']);
60+
expect(result).toEqual(['.test1, .test1 *, .test2, .test2 *']);
61+
});
62+
4663
it('should merge consecutive selectors with star variants', () => {
47-
const result = processCSSRuleChain(['.test1', '.test2']);
48-
expect(result).toBe('.test1, .test1 *, .test2, .test2 *');
64+
const result = processCSSSelectors(['.test1', '.test2']);
65+
expect(result).toEqual(['.test1, .test1 *, .test2, .test2 *']);
4966
});
5067

5168
it('should stack at-rules separately', () => {
52-
const result = processCSSRuleChain([
69+
const result = processCSSSelectors([
5370
'@media (prefers-color-scheme: dark)',
5471
'@media (max-width: 768px)',
5572
]);
@@ -60,7 +77,7 @@ describe('processCSSRuleChain', () => {
6077
});
6178

6279
it('should handle mixed selectors and at-rules', () => {
63-
const result = processCSSRuleChain([
80+
const result = processCSSSelectors([
6481
'.dark',
6582
'@media (prefers-color-scheme: dark)',
6683
'.custom',
@@ -73,55 +90,35 @@ describe('processCSSRuleChain', () => {
7390
});
7491

7592
it('should disable star variants when addStarVariants is false', () => {
76-
const result = processCSSRuleChain(['.test1', '.test2'], false);
77-
expect(result).toBe('.test1, .test2');
93+
const result = processCSSSelectors(['.test1', '.test2'], { addStarVariants: false });
94+
expect(result).toEqual(['.test1, .test2']);
7895
});
7996

8097
it('should return undefined for empty array', () => {
81-
expect(processCSSRuleChain([])).toBeUndefined();
98+
expect(processCSSSelectors([])).toBeUndefined();
8299
});
83100

84101
it('should expand selector aliases', () => {
85-
const result = processCSSRuleChain(['.test', 'media']);
102+
const result = processCSSSelectors(['.test', 'media']);
86103
expect(result).toEqual([
87104
'.test, .test *',
88105
'@media (prefers-color-scheme: dark)',
89106
]);
90107
});
91-
});
92-
93-
describe('processCSSSelectors', () => {
94-
it('should handle single string selectors', () => {
95-
expect(processCSSSelectors('.test')).toBe('.test, .test *');
96-
});
97-
98-
it('should handle single string with commas when allowCommaPassthrough is true', () => {
99-
expect(processCSSSelectors('.test, .other')).toBe('.test, .other');
100-
});
101-
102-
it('should add star variants when allowCommaPassthrough is false', () => {
103-
expect(processCSSSelectors('.test, .other', { allowCommaPassthrough: false }))
104-
.toBe('.test, .other, .test, .other *');
105-
});
106-
107-
it('should handle arrays with consecutive selectors', () => {
108-
const result = processCSSSelectors(['.test1', '.test2']);
109-
expect(result).toBe('.test1, .test1 *, .test2, .test2 *');
110-
});
111108

112109
it('should return undefined for empty arrays', () => {
113110
expect(processCSSSelectors([])).toBeUndefined();
114111
});
115112

116113
it('should expand aliases in single strings', () => {
117114
expect(processCSSSelectors('media'))
118-
.toBe('@media (prefers-color-scheme: dark), @media (prefers-color-scheme: dark) *');
115+
.toEqual(['@media (prefers-color-scheme: dark)']);
119116
});
120117

121118
it('should use custom aliases when provided', () => {
122119
const customAliases = { custom: '@media (min-width: 1200px)' };
123120
const result = processCSSSelectors('custom', { aliases: customAliases });
124-
expect(result).toBe('@media (min-width: 1200px), @media (min-width: 1200px) *');
121+
expect(result).toEqual(['@media (min-width: 1200px)']);
125122
});
126123

127124
it('should expand aliases in arrays', () => {

packages/@poupe-css/src/selectors.ts

Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,43 @@ export function expandSelectorAlias(
2424
return aliases[trimmed] || trimmed;
2525
}
2626

27+
export interface ProcessCSSSelectorOptions {
28+
/** Whether to add "selector *" variants to each selector */
29+
addStarVariants?: boolean
30+
/** Whether to allow comma-separated selectors to pass through */
31+
allowCommaPassthrough?: boolean
32+
/** Custom selector aliases to use for expansion */
33+
aliases?: Record<string, string>
34+
}
35+
2736
/**
28-
* Processes an array of CSS selectors and at-rules, merging consecutive
29-
* selectors with OR and adding * variants, while keeping at-rules stacked
30-
* separately.
37+
* Processes CSS selectors and at-rules, handling both strings and arrays.
38+
* Merges consecutive selectors with OR and adds * variants,
39+
* while keeping at-rules stacked separately.
3140
*
32-
* @param selectors - Array of CSS selectors and at-rules
33-
* @param addStarVariants - Whether to add "selector *" variants
34-
* @returns Single string, array of strings, or undefined
41+
* @param selectors - CSS selector(s) and at-rules
42+
* @param options - Processing options
43+
* @returns Array of processed selector strings or undefined
3544
*/
36-
export function processCSSRuleChain(
37-
selectors: string[],
38-
addStarVariants: boolean = true,
39-
): string | string[] | undefined {
45+
export function processCSSSelectors(
46+
selectors: string | string[],
47+
options: ProcessCSSSelectorOptions = {},
48+
): string[] | undefined {
49+
const {
50+
addStarVariants = true,
51+
allowCommaPassthrough = true,
52+
aliases = DEFAULT_SELECTOR_ALIASES,
53+
} = options;
54+
55+
// Convert string to array for unified processing
56+
const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
57+
58+
// Handle comma passthrough for single strings
59+
if (!Array.isArray(selectors) && allowCommaPassthrough && selectors.includes(',')) {
60+
const expanded = expandSelectorAlias(selectors, aliases);
61+
return [expanded];
62+
}
63+
4064
const result: string[] = [];
4165
const currentSelectors: string[] = [];
4266

@@ -55,8 +79,8 @@ export function processCSSRuleChain(
5579
}
5680
};
5781

58-
for (const s of selectors) {
59-
const expanded = expandSelectorAlias(s);
82+
for (const s of selectorArray) {
83+
const expanded = expandSelectorAlias(s, aliases);
6084
const trimmed = expanded.trim();
6185
if (!trimmed) continue;
6286

@@ -73,47 +97,5 @@ export function processCSSRuleChain(
7397
// Flush any remaining selectors
7498
flushSelectors();
7599

76-
if (result.length === 0) {
77-
return undefined;
78-
}
79-
return result.length === 1 ? result[0] : result;
80-
}
81-
82-
export interface ProcessCSSSelectorOptions {
83-
/** Whether to add "selector *" variants to each selector */
84-
addStarVariants?: boolean
85-
/** Whether to allow comma-separated selectors to pass through */
86-
allowCommaPassthrough?: boolean
87-
/** Custom selector aliases to use for expansion */
88-
aliases?: Record<string, string>
89-
}
90-
91-
/**
92-
* Generic function to process CSS selector arrays for any theme mode
93-
* @param selectors - Array of CSS selectors and at-rules
94-
* @param options - Processing options
95-
* @returns Processed selector string(s) or undefined
96-
*/
97-
export function processCSSSelectors(
98-
selectors: string | string[],
99-
options: ProcessCSSSelectorOptions = {},
100-
): string | string[] | undefined {
101-
const {
102-
addStarVariants = true,
103-
allowCommaPassthrough = true,
104-
aliases,
105-
} = options;
106-
107-
if (!Array.isArray(selectors)) {
108-
const expanded = expandSelectorAlias(selectors, aliases);
109-
110-
if (allowCommaPassthrough && expanded.includes(',')) {
111-
return expanded;
112-
}
113-
return addStarVariants
114-
? `${expanded}, ${expanded} *`
115-
: expanded;
116-
}
117-
118-
return processCSSRuleChain(selectors, addStarVariants);
100+
return result.length === 0 ? undefined : result;
119101
}

0 commit comments

Comments
 (0)