-
Notifications
You must be signed in to change notification settings - Fork 59
/
compileCSS.ts
122 lines (96 loc) · 3.85 KB
/
compileCSS.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { compile, middleware, prefixer, rulesheet, serialize, stringify } from 'stylis';
import { hyphenateProperty } from './utils/hyphenateProperty';
import { normalizeNestedProperty } from './utils/normalizeNestedProperty';
export interface CompileCSSOptions {
className: string;
pseudo: string;
media: string;
layer: string;
support: string;
property: string;
value: number | string | Array<number | string>;
rtlClassName?: string;
rtlProperty?: string;
rtlValue?: number | string | Array<number | string>;
}
const PSEUDO_SELECTOR_REGEX = /,( *[^ &])/g;
/**
* Normalizes pseudo selectors to always contain &, requires to work properly with comma-separated selectors.
*
* @example
* ":hover" => "&:hover"
* " :hover" => "& :hover"
* ":hover,:focus" => "&:hover,&:focus"
* " :hover, :focus" => "& :hover,& :focus"
*/
export function normalizePseudoSelector(pseudoSelector: string): string {
return (
'&' +
normalizeNestedProperty(
// Regex there replaces a comma, spaces and an ampersand if it's present with comma and an ampersand.
// This allows to normalize input, see examples in JSDoc.
pseudoSelector.replace(PSEUDO_SELECTOR_REGEX, ',&$1'),
)
);
}
export function compileCSSRules(cssRules: string): string[] {
const rules: string[] = [];
serialize(
compile(cssRules),
middleware([
prefixer,
stringify,
// 💡 we are using `.insertRule()` API for DOM operations, which does not support
// insertion of multiple CSS rules in a single call. `rulesheet` plugin extracts
// individual rules to be used with this API
rulesheet(rule => rules.push(rule)),
]),
);
return rules;
}
export function compileCSS(options: CompileCSSOptions): [string /* ltr definition */, string? /* rtl definition */] {
const { className, media, layer, pseudo, support, property, rtlClassName, rtlProperty, rtlValue, value } = options;
const classNameSelector = `.${className}`;
const cssDeclaration = Array.isArray(value)
? `{ ${value.map(v => `${hyphenateProperty(property)}: ${v}`).join(';')}; }`
: `{ ${hyphenateProperty(property)}: ${value}; }`;
let rtlClassNameSelector: string | null = null;
let rtlCSSDeclaration: string | null = null;
if (rtlProperty && rtlClassName) {
rtlClassNameSelector = `.${rtlClassName}`;
rtlCSSDeclaration = Array.isArray(rtlValue)
? `{ ${rtlValue.map(v => `${hyphenateProperty(rtlProperty)}: ${v}`).join(';')}; }`
: `{ ${hyphenateProperty(rtlProperty)}: ${rtlValue}; }`;
}
let cssRule = '';
// Should be handled by namespace plugin of Stylis, is buggy now
// Issues are reported:
// https://github.com/thysultan/stylis.js/issues/253
// https://github.com/thysultan/stylis.js/issues/252
if (pseudo.indexOf(':global(') === 0) {
// 👇 :global(GROUP_1)GROUP_2
const GLOBAL_PSEUDO_REGEX = /global\((.+)\)(.+)?/;
const [, globalSelector, restPseudo = ''] = GLOBAL_PSEUDO_REGEX.exec(pseudo)!;
// should be normalized to handle ":global(SELECTOR) &"
const normalizedPseudo = normalizeNestedProperty(restPseudo.trim());
const ltrRule = `${classNameSelector}${normalizedPseudo} ${cssDeclaration}`;
const rtlRule = rtlProperty ? `${rtlClassNameSelector}${normalizedPseudo} ${rtlCSSDeclaration}` : '';
cssRule = `${globalSelector} { ${ltrRule}; ${rtlRule} }`;
} else {
const normalizedPseudo = normalizePseudoSelector(pseudo);
cssRule = `${classNameSelector}{${normalizedPseudo} ${cssDeclaration}};`;
if (rtlProperty) {
cssRule = `${cssRule}; ${rtlClassNameSelector}${normalizedPseudo} ${rtlCSSDeclaration};`;
}
}
if (media) {
cssRule = `@media ${media} { ${cssRule} }`;
}
if (layer) {
cssRule = `@layer ${layer} { ${cssRule} }`;
}
if (support) {
cssRule = `@supports ${support} { ${cssRule} }`;
}
return compileCSSRules(cssRule) as [string, string?];
}