esm-styles 0.1.0
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/README.md +155 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +18 -0
- package/dist/lib/getCss.d.ts +14 -0
- package/dist/lib/getCss.js +247 -0
- package/dist/lib/types/index.d.ts +43 -0
- package/dist/lib/types/index.js +4 -0
- package/dist/lib/utils/common.d.ts +28 -0
- package/dist/lib/utils/common.js +47 -0
- package/dist/lib/utils/content.d.ts +9 -0
- package/dist/lib/utils/content.js +33 -0
- package/dist/lib/utils/media.d.ts +24 -0
- package/dist/lib/utils/media.js +66 -0
- package/dist/lib/utils/obj2css.d.ts +15 -0
- package/dist/lib/utils/obj2css.js +79 -0
- package/dist/lib/utils/selectors.d.ts +15 -0
- package/dist/lib/utils/selectors.js +87 -0
- package/dist/lib/utils/tags.d.ts +10 -0
- package/dist/lib/utils/tags.js +128 -0
- package/dist/lib/utils/traversal.d.ts +25 -0
- package/dist/lib/utils/traversal.js +78 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# ESM Styles
|
|
2
|
+
|
|
3
|
+
A TypeScript library for converting JavaScript objects to CSS strings, allowing for a cleaner syntax when writing CSS-in-JS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install esm-styles
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import { getCss } from 'esm-styles'
|
|
17
|
+
|
|
18
|
+
const styles = {
|
|
19
|
+
body: {
|
|
20
|
+
margin: 0,
|
|
21
|
+
padding: 0,
|
|
22
|
+
fontFamily: 'sans-serif',
|
|
23
|
+
|
|
24
|
+
header: {
|
|
25
|
+
backgroundColor: '#333',
|
|
26
|
+
color: 'white',
|
|
27
|
+
padding: '1rem',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
'main, article': {
|
|
31
|
+
maxWidth: '800px',
|
|
32
|
+
margin: '0 auto',
|
|
33
|
+
padding: '1rem',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
footer: {
|
|
37
|
+
textAlign: 'center',
|
|
38
|
+
padding: '1rem',
|
|
39
|
+
backgroundColor: '#f5f5f5',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const css = getCss(styles)
|
|
45
|
+
console.log(css)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This will output CSS with nested selectors properly expanded:
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
body {
|
|
52
|
+
margin: 0;
|
|
53
|
+
padding: 0;
|
|
54
|
+
font-family: sans-serif;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
body header {
|
|
58
|
+
background-color: #333;
|
|
59
|
+
color: white;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body main,
|
|
64
|
+
body article {
|
|
65
|
+
max-width: 800px;
|
|
66
|
+
margin: 0 auto;
|
|
67
|
+
padding: 1rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
body footer {
|
|
71
|
+
text-align: center;
|
|
72
|
+
padding: 1rem;
|
|
73
|
+
background-color: #f5f5f5;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Advanced Features
|
|
78
|
+
|
|
79
|
+
#### Media Queries
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
import { getCss } from 'esm-styles'
|
|
83
|
+
|
|
84
|
+
const styles = {
|
|
85
|
+
body: {
|
|
86
|
+
fontSize: '16px',
|
|
87
|
+
|
|
88
|
+
'@media (max-width: 768px)': {
|
|
89
|
+
fontSize: '14px',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const css = getCss(styles)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Named Media Queries
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
import { getCss } from 'esm-styles'
|
|
101
|
+
|
|
102
|
+
const styles = {
|
|
103
|
+
container: {
|
|
104
|
+
width: '1200px',
|
|
105
|
+
'@tablet': {
|
|
106
|
+
width: '100%',
|
|
107
|
+
padding: '0 20px',
|
|
108
|
+
},
|
|
109
|
+
'@mobile': {
|
|
110
|
+
padding: '0 10px',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mediaQueries = {
|
|
116
|
+
tablet: '(max-width: 1024px)',
|
|
117
|
+
mobile: '(max-width: 480px)',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const css = getCss(styles, mediaQueries)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Class and Tag Selectors
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import { getCss } from 'esm-styles'
|
|
127
|
+
|
|
128
|
+
const styles = {
|
|
129
|
+
// Tag selector (div)
|
|
130
|
+
div: {
|
|
131
|
+
margin: '10px',
|
|
132
|
+
|
|
133
|
+
// Nested tag selector (p inside div)
|
|
134
|
+
p: {
|
|
135
|
+
lineHeight: 1.5,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Class selector (with underscore prefix)
|
|
139
|
+
_highlight: {
|
|
140
|
+
backgroundColor: 'yellow',
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Descendant class selector (with double underscore)
|
|
144
|
+
__text: {
|
|
145
|
+
color: 'blue',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const css = getCss(styles)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esm-styles
|
|
3
|
+
*
|
|
4
|
+
* A library for working with CSS styles in ESM
|
|
5
|
+
*/
|
|
6
|
+
export * from './lib/types/index.js';
|
|
7
|
+
export { default as getCss } from './lib/getCss.js';
|
|
8
|
+
export { joinSelectors, cartesianSelectors } from './lib/utils/selectors.js';
|
|
9
|
+
export { formatContentValue } from './lib/utils/content.js';
|
|
10
|
+
export { jsKeyToCssKey, isEndValue } from './lib/utils/common.js';
|
|
11
|
+
export { processMediaQueries } from './lib/utils/media.js';
|
|
12
|
+
export { obj2css, prettifyCss } from './lib/utils/obj2css.js';
|
|
13
|
+
export declare function greet(): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esm-styles
|
|
3
|
+
*
|
|
4
|
+
* A library for working with CSS styles in ESM
|
|
5
|
+
*/
|
|
6
|
+
// Export types
|
|
7
|
+
export * from './lib/types/index.js';
|
|
8
|
+
// Main function export
|
|
9
|
+
export { default as getCss } from './lib/getCss.js';
|
|
10
|
+
// Utils exports for advanced usage
|
|
11
|
+
export { joinSelectors, cartesianSelectors } from './lib/utils/selectors.js';
|
|
12
|
+
export { formatContentValue } from './lib/utils/content.js';
|
|
13
|
+
export { jsKeyToCssKey, isEndValue } from './lib/utils/common.js';
|
|
14
|
+
export { processMediaQueries } from './lib/utils/media.js';
|
|
15
|
+
export { obj2css, prettifyCss } from './lib/utils/obj2css.js';
|
|
16
|
+
export function greet() {
|
|
17
|
+
return 'Hello from esm-styles!';
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main function for converting JavaScript objects to CSS
|
|
3
|
+
*/
|
|
4
|
+
import { CssStyles, MediaQueries, MediaPrefixes, AutoConfig } from './types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Converts a JavaScript style object to CSS string
|
|
7
|
+
* @param object - The JavaScript object to convert to CSS
|
|
8
|
+
* @param mediaQueries - Media query definitions (e.g., { 'phone': '(max-width: 499px)' })
|
|
9
|
+
* @param mediaPrefixes - Media prefix definitions (e.g., { 'dark': ':root.dark' })
|
|
10
|
+
* @param auto - Auto mode configuration (e.g., { 'dark': [':root.auto', 'screen and (prefers-color-scheme: dark)'] })
|
|
11
|
+
* @returns CSS string
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCss(object: CssStyles, mediaQueries?: MediaQueries, mediaPrefixes?: MediaPrefixes, auto?: AutoConfig): string;
|
|
14
|
+
export default getCss;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main function for converting JavaScript objects to CSS
|
|
3
|
+
*/
|
|
4
|
+
import { traverseObject, determineNodeType } from './utils/traversal.js';
|
|
5
|
+
import { indent } from './utils/common.js';
|
|
6
|
+
import { obj2css, prettifyCss } from './utils/obj2css.js';
|
|
7
|
+
import { joinSelectors, cartesianSelectors } from './utils/selectors.js';
|
|
8
|
+
import { formatContentValue } from './utils/content.js';
|
|
9
|
+
import { jsKeyToCssKey } from './utils/common.js';
|
|
10
|
+
import { processMediaQueries } from './utils/media.js';
|
|
11
|
+
/**
|
|
12
|
+
* Converts a JavaScript style object to CSS string
|
|
13
|
+
* @param object - The JavaScript object to convert to CSS
|
|
14
|
+
* @param mediaQueries - Media query definitions (e.g., { 'phone': '(max-width: 499px)' })
|
|
15
|
+
* @param mediaPrefixes - Media prefix definitions (e.g., { 'dark': ':root.dark' })
|
|
16
|
+
* @param auto - Auto mode configuration (e.g., { 'dark': [':root.auto', 'screen and (prefers-color-scheme: dark)'] })
|
|
17
|
+
* @returns CSS string
|
|
18
|
+
*/
|
|
19
|
+
export function getCss(object, mediaQueries = {}, mediaPrefixes = {}, auto) {
|
|
20
|
+
// Initialize various objects to collect different types of CSS rules
|
|
21
|
+
let cssStyle = {};
|
|
22
|
+
const layerStatements = [];
|
|
23
|
+
let layerObject = {};
|
|
24
|
+
let containerObject = {};
|
|
25
|
+
let mediaObject = {};
|
|
26
|
+
let prefixObject = {};
|
|
27
|
+
// Process the object by traversing it and handling different node types
|
|
28
|
+
traverseObject(object, (node, path, _, __) => {
|
|
29
|
+
if (!path)
|
|
30
|
+
return node;
|
|
31
|
+
const nodeType = determineNodeType(node, path);
|
|
32
|
+
const pathParts = path.split('\\');
|
|
33
|
+
const key = pathParts.pop() || '';
|
|
34
|
+
switch (nodeType) {
|
|
35
|
+
case 'selector': {
|
|
36
|
+
// Handle CSS property-value pairs
|
|
37
|
+
const selector = joinSelectors(pathParts);
|
|
38
|
+
// Create cartesian product of selectors for comma-separated parts
|
|
39
|
+
if (pathParts.some((part) => part.includes(','))) {
|
|
40
|
+
const classPaths = pathParts.map((part) => part.split(',').map((p) => p.trim()));
|
|
41
|
+
const allPaths = cartesianSelectors(classPaths);
|
|
42
|
+
// Apply the property-value to each selector
|
|
43
|
+
allPaths.forEach((selectorPath) => {
|
|
44
|
+
const cssKey = jsKeyToCssKey(key);
|
|
45
|
+
let value = node;
|
|
46
|
+
// Special handling for content property
|
|
47
|
+
if (cssKey === 'content') {
|
|
48
|
+
value = formatContentValue(value);
|
|
49
|
+
}
|
|
50
|
+
// Create the CSS object and merge it
|
|
51
|
+
const cssObject = {
|
|
52
|
+
[selectorPath]: { [cssKey]: value },
|
|
53
|
+
};
|
|
54
|
+
cssStyle = mergeDeep(cssStyle, cssObject);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Simple case - no commas in selectors
|
|
59
|
+
const cssKey = jsKeyToCssKey(key);
|
|
60
|
+
let value = node;
|
|
61
|
+
// Special handling for content property
|
|
62
|
+
if (cssKey === 'content') {
|
|
63
|
+
value = formatContentValue(value);
|
|
64
|
+
}
|
|
65
|
+
// Create the CSS object and merge it
|
|
66
|
+
const cssObject = {
|
|
67
|
+
[selector]: { [cssKey]: value },
|
|
68
|
+
};
|
|
69
|
+
cssStyle = mergeDeep(cssStyle, cssObject);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'layer statement': {
|
|
74
|
+
// Handle @layer statements
|
|
75
|
+
const statement = `${key}${node ? ' ' + node : ''};`;
|
|
76
|
+
if (!layerStatements.includes(statement)) {
|
|
77
|
+
layerStatements.push(statement);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'layer block': {
|
|
82
|
+
// Handle @layer blocks
|
|
83
|
+
const selector = joinSelectors(pathParts);
|
|
84
|
+
const object = { [key]: { [selector]: node } };
|
|
85
|
+
layerObject = mergeDeep(layerObject, object);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'container query block': {
|
|
89
|
+
// Handle @container queries
|
|
90
|
+
const selector = joinSelectors(pathParts);
|
|
91
|
+
const object = { [key]: { [selector]: node } };
|
|
92
|
+
containerObject = mergeDeep(containerObject, object);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'media query or prefix': {
|
|
96
|
+
// Handle media queries and prefixes
|
|
97
|
+
const selector = joinSelectors(pathParts);
|
|
98
|
+
const name = key.replace(/^@\s*/, '');
|
|
99
|
+
if (mediaPrefixes[name]) {
|
|
100
|
+
// Handle media prefix
|
|
101
|
+
const prefix = mediaPrefixes[name];
|
|
102
|
+
const rules = selector ? { ['& ' + selector]: node } : node;
|
|
103
|
+
const mediaPrefixObject = { [prefix]: rules };
|
|
104
|
+
prefixObject = mergeDeep(prefixObject, mediaPrefixObject);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Handle media query
|
|
108
|
+
let mediaQuery = key;
|
|
109
|
+
if (key.startsWith('@media ')) {
|
|
110
|
+
// Use the media query as is, just remove @media prefix
|
|
111
|
+
mediaQuery = key.replace(/^@media\s+/, '');
|
|
112
|
+
}
|
|
113
|
+
else if (mediaQueries[name]) {
|
|
114
|
+
// Use the named media query from configuration
|
|
115
|
+
mediaQuery = mediaQueries[name];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Unknown media query type
|
|
119
|
+
console.warn(`Warning: Media query type ${key} is unknown`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
const mediaQueryObject = {
|
|
123
|
+
['@media ' + mediaQuery]: { [selector]: node },
|
|
124
|
+
};
|
|
125
|
+
mediaObject = mergeDeep(mediaObject, mediaQueryObject);
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return node;
|
|
131
|
+
});
|
|
132
|
+
// Convert the main CSS style object to string
|
|
133
|
+
let cssString = obj2css(cssStyle);
|
|
134
|
+
// Add layer statements if any
|
|
135
|
+
if (layerStatements.length > 0) {
|
|
136
|
+
cssString = layerStatements.join('\n') + '\n\n' + cssString;
|
|
137
|
+
}
|
|
138
|
+
// Process and add layer blocks if any
|
|
139
|
+
if (Object.keys(layerObject).length > 0) {
|
|
140
|
+
const layers = Object.keys(layerObject);
|
|
141
|
+
const layerCssString = layers
|
|
142
|
+
.map((layer) => {
|
|
143
|
+
// Type guard for object values
|
|
144
|
+
const layerStyles = layerObject[layer];
|
|
145
|
+
if (!isObject(layerStyles))
|
|
146
|
+
return '';
|
|
147
|
+
const layerContent = getCss(layerStyles, mediaQueries, mediaPrefixes, auto);
|
|
148
|
+
return layerContent ? `${layer} {\n${indent(layerContent)}\n}` : '';
|
|
149
|
+
})
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.join('\n\n');
|
|
152
|
+
if (layerCssString) {
|
|
153
|
+
cssString += '\n\n' + layerCssString;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Process and add container queries if any
|
|
157
|
+
if (Object.keys(containerObject).length > 0) {
|
|
158
|
+
const containerQueries = Object.keys(containerObject);
|
|
159
|
+
const containerCssString = containerQueries
|
|
160
|
+
.map((containerQuery) => {
|
|
161
|
+
// Type guard for object values
|
|
162
|
+
const containerStyles = containerObject[containerQuery];
|
|
163
|
+
if (!isObject(containerStyles))
|
|
164
|
+
return '';
|
|
165
|
+
const containerContent = getCss(containerStyles, mediaQueries, mediaPrefixes, auto);
|
|
166
|
+
return containerContent
|
|
167
|
+
? `${containerQuery} {\n${indent(containerContent)}\n}`
|
|
168
|
+
: '';
|
|
169
|
+
})
|
|
170
|
+
.filter(Boolean)
|
|
171
|
+
.join('\n\n');
|
|
172
|
+
if (containerCssString) {
|
|
173
|
+
cssString += '\n\n' + containerCssString;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Process and add media queries if any
|
|
177
|
+
if (Object.keys(mediaObject).length > 0) {
|
|
178
|
+
const mediaCssString = processMediaQueries(mediaObject, mediaQueries);
|
|
179
|
+
if (mediaCssString) {
|
|
180
|
+
cssString += '\n\n' + mediaCssString;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Process and add media prefixes if any
|
|
184
|
+
if (Object.keys(prefixObject).length > 0) {
|
|
185
|
+
// First, process the prefix object as normal CSS
|
|
186
|
+
const mediaPrefixedCssString = getCss(prefixObject, mediaQueries, mediaPrefixes);
|
|
187
|
+
cssString += '\n\n' + mediaPrefixedCssString;
|
|
188
|
+
// Handle auto mode if configured
|
|
189
|
+
if (auto) {
|
|
190
|
+
// Create a modified prefix object for auto mode
|
|
191
|
+
const autoPrefixObject = {};
|
|
192
|
+
for (const key of Object.keys(auto)) {
|
|
193
|
+
const selector = mediaPrefixes[key];
|
|
194
|
+
if (!selector || !prefixObject[selector])
|
|
195
|
+
continue;
|
|
196
|
+
const [autoSelector, mediaQuery] = auto[key];
|
|
197
|
+
// Create media query for auto mode
|
|
198
|
+
autoPrefixObject[`@media ${mediaQuery}`] = {
|
|
199
|
+
[autoSelector]: prefixObject[selector],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Process the auto prefix object if not empty
|
|
203
|
+
if (Object.keys(autoPrefixObject).length > 0) {
|
|
204
|
+
const autoCssString = processMediaQueries(autoPrefixObject, mediaQueries);
|
|
205
|
+
if (autoCssString) {
|
|
206
|
+
cssString += '\n\n' + autoCssString;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Prettify the final CSS string
|
|
212
|
+
return prettifyCss(cssString.replace(/__bs__/g, '\\'));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Deep merge two objects
|
|
216
|
+
* @param target - Target object to merge into
|
|
217
|
+
* @param source - Source object to merge from
|
|
218
|
+
* @returns Merged object
|
|
219
|
+
*/
|
|
220
|
+
function mergeDeep(target, source) {
|
|
221
|
+
const output = { ...target };
|
|
222
|
+
if (isObject(target) && isObject(source)) {
|
|
223
|
+
Object.keys(source).forEach((key) => {
|
|
224
|
+
if (isObject(source[key])) {
|
|
225
|
+
if (!(key in target)) {
|
|
226
|
+
output[key] = source[key];
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
output[key] = mergeDeep(target[key], source[key]);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
output[key] = source[key];
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return output;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Checks if value is a non-null object
|
|
241
|
+
* @param item - Value to check
|
|
242
|
+
* @returns True if the value is a non-null object
|
|
243
|
+
*/
|
|
244
|
+
function isObject(item) {
|
|
245
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
246
|
+
}
|
|
247
|
+
export default getCss;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the CSS in JS library
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* CSS property value - can be string, number, or boolean
|
|
6
|
+
*/
|
|
7
|
+
export type CssValue = string | number | boolean | null;
|
|
8
|
+
/**
|
|
9
|
+
* CSS selector or CSS property name
|
|
10
|
+
*/
|
|
11
|
+
export type CssKey = string;
|
|
12
|
+
/**
|
|
13
|
+
* CSS styles object that can contain nested selectors or properties
|
|
14
|
+
*/
|
|
15
|
+
export interface CssStyles {
|
|
16
|
+
[key: CssKey]: CssValue | CssStyles;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Named media queries configuration
|
|
20
|
+
*/
|
|
21
|
+
export interface MediaQueries {
|
|
22
|
+
[name: string]: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Media prefixes configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface MediaPrefixes {
|
|
28
|
+
[name: string]: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Auto mode configuration for color schemes
|
|
32
|
+
*/
|
|
33
|
+
export interface AutoConfig {
|
|
34
|
+
[mode: string]: [string, string];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for the CSS generator
|
|
38
|
+
*/
|
|
39
|
+
export interface CssConfig {
|
|
40
|
+
mediaQueries?: MediaQueries;
|
|
41
|
+
mediaPrefixes?: MediaPrefixes;
|
|
42
|
+
auto?: AutoConfig;
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for CSS processing
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Determines the object type in a more precise way than typeof
|
|
6
|
+
* @param obj - Any value to check type
|
|
7
|
+
* @returns The lowercase string representing the object type
|
|
8
|
+
*/
|
|
9
|
+
export declare function getObjectType(obj: any): string;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a value is a primitive end value (string, number, boolean, null)
|
|
12
|
+
* @param value - Value to check
|
|
13
|
+
* @returns True if the value is a primitive end value
|
|
14
|
+
*/
|
|
15
|
+
export declare function isEndValue(value: any): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Indents a string with spaces
|
|
18
|
+
* @param str - String to indent
|
|
19
|
+
* @param spaces - Number of spaces to indent with
|
|
20
|
+
* @returns Indented string
|
|
21
|
+
*/
|
|
22
|
+
export declare function indent(str: string, spaces?: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Converts a JavaScript property name in camelCase to CSS kebab-case
|
|
25
|
+
* @param key - Property name in camelCase
|
|
26
|
+
* @returns Property name in kebab-case
|
|
27
|
+
*/
|
|
28
|
+
export declare function jsKeyToCssKey(key: string): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for CSS processing
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Determines the object type in a more precise way than typeof
|
|
6
|
+
* @param obj - Any value to check type
|
|
7
|
+
* @returns The lowercase string representing the object type
|
|
8
|
+
*/
|
|
9
|
+
export function getObjectType(obj) {
|
|
10
|
+
return (Object.prototype.toString
|
|
11
|
+
.call(obj)
|
|
12
|
+
.match(/^\[object (\w+)\]$/)?.[1]
|
|
13
|
+
.toLowerCase() || 'unknown');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a value is a primitive end value (string, number, boolean, null)
|
|
17
|
+
* @param value - Value to check
|
|
18
|
+
* @returns True if the value is a primitive end value
|
|
19
|
+
*/
|
|
20
|
+
export function isEndValue(value) {
|
|
21
|
+
return ['string', 'number', 'boolean', 'null'].includes(getObjectType(value));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Indents a string with spaces
|
|
25
|
+
* @param str - String to indent
|
|
26
|
+
* @param spaces - Number of spaces to indent with
|
|
27
|
+
* @returns Indented string
|
|
28
|
+
*/
|
|
29
|
+
export function indent(str, spaces = 2) {
|
|
30
|
+
return str
|
|
31
|
+
.split('\n')
|
|
32
|
+
.map((s) => ' '.repeat(spaces) + s)
|
|
33
|
+
.join('\n');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Converts a JavaScript property name in camelCase to CSS kebab-case
|
|
37
|
+
* @param key - Property name in camelCase
|
|
38
|
+
* @returns Property name in kebab-case
|
|
39
|
+
*/
|
|
40
|
+
export function jsKeyToCssKey(key) {
|
|
41
|
+
// Keep vendor prefixes intact
|
|
42
|
+
const prefix = key.match(/^[-]+/)?.[0] || '';
|
|
43
|
+
const baseKey = key.replace(/^[-]+/, '');
|
|
44
|
+
// Convert camelCase to kebab-case
|
|
45
|
+
const kebabKey = baseKey.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
46
|
+
return prefix + kebabKey;
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for handling CSS content property values
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts a JavaScript string value to a valid CSS content property value
|
|
6
|
+
* @param value - String value to convert for CSS content property
|
|
7
|
+
* @returns CSS-compatible content value
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatContentValue(value: string | number | boolean | null): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for handling CSS content property values
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts a JavaScript string value to a valid CSS content property value
|
|
6
|
+
* @param value - String value to convert for CSS content property
|
|
7
|
+
* @returns CSS-compatible content value
|
|
8
|
+
*/
|
|
9
|
+
export function formatContentValue(value) {
|
|
10
|
+
if (value === null)
|
|
11
|
+
return 'none';
|
|
12
|
+
const stringValue = String(value);
|
|
13
|
+
// If it's already wrapped in quotes, return as is
|
|
14
|
+
if (/^['"].*['"]$/.test(stringValue)) {
|
|
15
|
+
return stringValue;
|
|
16
|
+
}
|
|
17
|
+
// Handle unicode and emoji characters
|
|
18
|
+
const formattedValue = Array.from(stringValue)
|
|
19
|
+
.map((char) => {
|
|
20
|
+
const codePoint = char.codePointAt(0);
|
|
21
|
+
if (!codePoint)
|
|
22
|
+
return char;
|
|
23
|
+
// Convert emoji and special characters to unicode escape sequences
|
|
24
|
+
// Control characters and non-ASCII characters need escaping
|
|
25
|
+
if (codePoint > 127 || codePoint < 32) {
|
|
26
|
+
return `\\${codePoint.toString(16).padStart(6, '0')}`;
|
|
27
|
+
}
|
|
28
|
+
return char;
|
|
29
|
+
})
|
|
30
|
+
.join('');
|
|
31
|
+
// Wrap in single quotes if not already wrapped
|
|
32
|
+
return `'${formattedValue}'`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for handling media queries
|
|
3
|
+
*/
|
|
4
|
+
import { CssStyles, MediaQueries } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Processes media query objects and converts them to CSS
|
|
7
|
+
* @param mediaObject - Object containing media queries and their styles
|
|
8
|
+
* @param mediaQueries - Named media query definitions
|
|
9
|
+
* @returns CSS string with processed media queries
|
|
10
|
+
*/
|
|
11
|
+
export declare function processMediaQueries(mediaObject: CssStyles, mediaQueries?: MediaQueries): string;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a key represents a media query
|
|
14
|
+
* @param key - Key to check
|
|
15
|
+
* @returns True if the key is a media query
|
|
16
|
+
*/
|
|
17
|
+
export declare function isMediaQuery(key: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a key is a named media query that needs to be resolved
|
|
20
|
+
* @param key - Key to check
|
|
21
|
+
* @param mediaQueries - Named media query definitions
|
|
22
|
+
* @returns True if the key is a named media query
|
|
23
|
+
*/
|
|
24
|
+
export declare function isNamedMediaQuery(key: string, mediaQueries: MediaQueries): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for handling media queries
|
|
3
|
+
*/
|
|
4
|
+
import { indent } from './common.js';
|
|
5
|
+
import { obj2css } from './obj2css.js';
|
|
6
|
+
import { isEndValue } from './common.js';
|
|
7
|
+
/**
|
|
8
|
+
* Processes media query objects and converts them to CSS
|
|
9
|
+
* @param mediaObject - Object containing media queries and their styles
|
|
10
|
+
* @param mediaQueries - Named media query definitions
|
|
11
|
+
* @returns CSS string with processed media queries
|
|
12
|
+
*/
|
|
13
|
+
export function processMediaQueries(mediaObject, mediaQueries = {}) {
|
|
14
|
+
if (!mediaObject || Object.keys(mediaObject).length === 0) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
const mediaQueriesToProcess = Object.keys(mediaObject);
|
|
18
|
+
// Process each media query and convert to CSS
|
|
19
|
+
const mediaCssStrings = mediaQueriesToProcess.map((queryKey) => {
|
|
20
|
+
// Handle named media queries (using @name syntax)
|
|
21
|
+
const mediaQueryMatch = queryKey.match(/^@(\w+)$/);
|
|
22
|
+
let actualQuery = queryKey;
|
|
23
|
+
if (mediaQueryMatch &&
|
|
24
|
+
mediaQueryMatch[1] &&
|
|
25
|
+
mediaQueries[mediaQueryMatch[1]]) {
|
|
26
|
+
// Replace named query with actual query definition
|
|
27
|
+
actualQuery = mediaQueries[mediaQueryMatch[1]];
|
|
28
|
+
}
|
|
29
|
+
else if (queryKey.startsWith('@media ')) {
|
|
30
|
+
// Strip @media prefix if present
|
|
31
|
+
actualQuery = queryKey.replace(/^@media\s+/, '');
|
|
32
|
+
}
|
|
33
|
+
// Convert the styles for this media query to CSS
|
|
34
|
+
const styles = mediaObject[queryKey];
|
|
35
|
+
// Skip if the styles aren't an object
|
|
36
|
+
if (isEndValue(styles)) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
const stylesCss = obj2css(styles);
|
|
40
|
+
if (!stylesCss.trim()) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
// Wrap the styles in a media query block
|
|
44
|
+
return `@media ${actualQuery} {\n${indent(stylesCss)}\n}`;
|
|
45
|
+
});
|
|
46
|
+
// Join all media query blocks with newlines
|
|
47
|
+
return mediaCssStrings.filter((css) => css).join('\n\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Checks if a key represents a media query
|
|
51
|
+
* @param key - Key to check
|
|
52
|
+
* @returns True if the key is a media query
|
|
53
|
+
*/
|
|
54
|
+
export function isMediaQuery(key) {
|
|
55
|
+
return key.startsWith('@media') || key.startsWith('@');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a key is a named media query that needs to be resolved
|
|
59
|
+
* @param key - Key to check
|
|
60
|
+
* @param mediaQueries - Named media query definitions
|
|
61
|
+
* @returns True if the key is a named media query
|
|
62
|
+
*/
|
|
63
|
+
export function isNamedMediaQuery(key, mediaQueries) {
|
|
64
|
+
const match = key.match(/^@(\w+)$/);
|
|
65
|
+
return Boolean(match && match[1] && mediaQueries[match[1]]);
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for converting JavaScript objects to CSS strings
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts a CSS object to a string representation
|
|
6
|
+
* @param cssObject - Object with CSS selectors and properties
|
|
7
|
+
* @returns CSS string
|
|
8
|
+
*/
|
|
9
|
+
export declare function obj2css(cssObject: Record<string, any>): string;
|
|
10
|
+
/**
|
|
11
|
+
* Prettifies a CSS string by ensuring consistent spacing and formatting
|
|
12
|
+
* @param cssString - CSS string to prettify
|
|
13
|
+
* @returns Prettified CSS string
|
|
14
|
+
*/
|
|
15
|
+
export declare function prettifyCss(cssString: string): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for converting JavaScript objects to CSS strings
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts a CSS object to a string representation
|
|
6
|
+
* @param cssObject - Object with CSS selectors and properties
|
|
7
|
+
* @returns CSS string
|
|
8
|
+
*/
|
|
9
|
+
export function obj2css(cssObject) {
|
|
10
|
+
// Convert object to JSON string with indentation
|
|
11
|
+
const json = JSON.stringify(cssObject, null, 2);
|
|
12
|
+
// Process the JSON string to convert to valid CSS
|
|
13
|
+
const css = json
|
|
14
|
+
// Replace double quotes with single quotes for strings
|
|
15
|
+
.replace(/\\"/g, "'")
|
|
16
|
+
// Convert object keys to CSS selectors
|
|
17
|
+
.replace(/"([^"]+)":\s*{/g, '$1 {')
|
|
18
|
+
// Remove commas between rule sets and add empty line
|
|
19
|
+
.replace(/},/g, '}')
|
|
20
|
+
// Remove remaining double quotes
|
|
21
|
+
.replace(/"/g, '')
|
|
22
|
+
// Restore quotes for inlined URLs
|
|
23
|
+
.replace(/url\('(.+)'\)/g, 'url("$1")')
|
|
24
|
+
// Convert property-value commas to semicolons
|
|
25
|
+
.replace(/,(\s*})/g, ';$1')
|
|
26
|
+
// Remove the outermost curly braces
|
|
27
|
+
.replace(/^{|}$/g, '')
|
|
28
|
+
// Convert commas between property values to semicolons
|
|
29
|
+
.replace(/,\s*(?=[a-z-]+:)/g, ';\n ')
|
|
30
|
+
// Remove indentation from the start of lines
|
|
31
|
+
.replace(/^( {2})/gm, '')
|
|
32
|
+
// Add semicolons before closing brackets if missing
|
|
33
|
+
.replace(/([^;])\s*}/g, '$1;\n}')
|
|
34
|
+
// Ensure space between property name and value
|
|
35
|
+
.replace(/:\s*/g, ': ')
|
|
36
|
+
// Clean up extra spaces
|
|
37
|
+
.replace(/\s+/g, ' ')
|
|
38
|
+
// Fix colon spacing in selectors
|
|
39
|
+
.replace(/([:#]) /g, '$1');
|
|
40
|
+
// Split into lines for additional processing
|
|
41
|
+
const lines = css.split('\n');
|
|
42
|
+
const result = [];
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const line = lines[i];
|
|
45
|
+
// Handle property lines
|
|
46
|
+
if (line.includes(': ')) {
|
|
47
|
+
result.push(' ' + line.trim());
|
|
48
|
+
}
|
|
49
|
+
// Handle selector lines
|
|
50
|
+
else if (line.includes('{')) {
|
|
51
|
+
result.push(line.trim());
|
|
52
|
+
}
|
|
53
|
+
// Handle closing bracket lines
|
|
54
|
+
else if (line.includes('}')) {
|
|
55
|
+
result.push('}');
|
|
56
|
+
}
|
|
57
|
+
// Other lines
|
|
58
|
+
else {
|
|
59
|
+
result.push(line);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result.join('\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Prettifies a CSS string by ensuring consistent spacing and formatting
|
|
66
|
+
* @param cssString - CSS string to prettify
|
|
67
|
+
* @returns Prettified CSS string
|
|
68
|
+
*/
|
|
69
|
+
export function prettifyCss(cssString) {
|
|
70
|
+
return (cssString
|
|
71
|
+
// Ensure consistent newlines between rule sets
|
|
72
|
+
.replace(/}\s*/g, '}\n\n')
|
|
73
|
+
// Fix spacing inside brackets
|
|
74
|
+
.replace(/{\s*/g, ' {\n')
|
|
75
|
+
// Clean up extra newlines
|
|
76
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
77
|
+
// Trim the string
|
|
78
|
+
.trim());
|
|
79
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for working with CSS selectors
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Joins selector parts into a valid CSS selector
|
|
6
|
+
* @param path - Array of selector parts or a single selector
|
|
7
|
+
* @returns Combined CSS selector
|
|
8
|
+
*/
|
|
9
|
+
export declare function joinSelectors(path: string | string[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a Cartesian product of selector parts and joins them
|
|
12
|
+
* @param parts - Array of selector parts arrays
|
|
13
|
+
* @returns Array of all possible combined selectors
|
|
14
|
+
*/
|
|
15
|
+
export declare function cartesianSelectors(parts: string[][]): string[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for working with CSS selectors
|
|
3
|
+
*/
|
|
4
|
+
import { isHtmlTag } from './tags.js';
|
|
5
|
+
/**
|
|
6
|
+
* Joins selector parts into a valid CSS selector
|
|
7
|
+
* @param path - Array of selector parts or a single selector
|
|
8
|
+
* @returns Combined CSS selector
|
|
9
|
+
*/
|
|
10
|
+
export function joinSelectors(path) {
|
|
11
|
+
const array = typeof path === 'string' ? [path] : path;
|
|
12
|
+
let result = '';
|
|
13
|
+
for (const keyStr of array) {
|
|
14
|
+
// Skip empty key strings
|
|
15
|
+
if (!keyStr.trim())
|
|
16
|
+
continue;
|
|
17
|
+
const key = keyStr.replace(/&/g, '').trim();
|
|
18
|
+
if (isHtmlTag(key)) {
|
|
19
|
+
// If it's an HTML tag, just add it with a space
|
|
20
|
+
result += ` ${key}`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
let prefix = '';
|
|
24
|
+
let selector = key;
|
|
25
|
+
// Handle reference to parent selector with &
|
|
26
|
+
if (/^&/.test(keyStr)) {
|
|
27
|
+
prefix += ' ';
|
|
28
|
+
}
|
|
29
|
+
// Handle double underscore for descendant of any level (T6)
|
|
30
|
+
if (/^__/.test(key)) {
|
|
31
|
+
prefix += ' ';
|
|
32
|
+
selector = selector.replace(/^__/, '');
|
|
33
|
+
}
|
|
34
|
+
// Handle single underscore for class selector (T5)
|
|
35
|
+
if (/^_/.test(key)) {
|
|
36
|
+
selector = selector.replace(/^_/, '');
|
|
37
|
+
if (!isHtmlTag(selector)) {
|
|
38
|
+
// If not a tag, increase level
|
|
39
|
+
prefix += ' ';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Add dot for class selectors if needed
|
|
43
|
+
if (!/^[.:#*[~+>]/.test(selector)) {
|
|
44
|
+
if (!/\./.test(prefix))
|
|
45
|
+
prefix += '.';
|
|
46
|
+
}
|
|
47
|
+
// Special handling for universal selector
|
|
48
|
+
if (/^\*/.test(selector)) {
|
|
49
|
+
prefix = ' ' + prefix;
|
|
50
|
+
}
|
|
51
|
+
result += prefix + selector;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Fix any double dots that might have been created
|
|
55
|
+
result = result.replace(/\.\s*\./g, '.');
|
|
56
|
+
// Clean up whitespace
|
|
57
|
+
result = result
|
|
58
|
+
// Remove extra spaces
|
|
59
|
+
.replace(/\s+/g, ' ')
|
|
60
|
+
// Preserve proper spacing around combinators
|
|
61
|
+
.replace(/\s*([>+~])\s*/g, ' $1 ')
|
|
62
|
+
// Fix pseudo-classes and pseudo-elements
|
|
63
|
+
.replace(/\s+:/g, ':')
|
|
64
|
+
// Fix attribute selectors
|
|
65
|
+
.replace(/\s+\[/g, '[')
|
|
66
|
+
// Trim whitespace
|
|
67
|
+
.trim();
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a Cartesian product of selector parts and joins them
|
|
72
|
+
* @param parts - Array of selector parts arrays
|
|
73
|
+
* @returns Array of all possible combined selectors
|
|
74
|
+
*/
|
|
75
|
+
export function cartesianSelectors(parts) {
|
|
76
|
+
// Base case: If empty or just one part, return it flattened
|
|
77
|
+
if (parts.length === 0)
|
|
78
|
+
return [];
|
|
79
|
+
if (parts.length === 1)
|
|
80
|
+
return parts[0];
|
|
81
|
+
// Implementation of cartesian product
|
|
82
|
+
const result = parts.reduce((acc, curr) => acc.flatMap((x) => curr.map((y) => [...x, y])), [[]]);
|
|
83
|
+
return result.map((selectors) => {
|
|
84
|
+
// Handle comma-separated selectors by creating real CSS selector lists
|
|
85
|
+
return joinSelectors(selectors.filter(Boolean));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List of all HTML tags used to identify selectors
|
|
3
|
+
*/
|
|
4
|
+
export declare const htmlTags: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a string is an HTML tag name
|
|
7
|
+
* @param key - String to check
|
|
8
|
+
* @returns True if the string is an HTML tag name
|
|
9
|
+
*/
|
|
10
|
+
export declare function isHtmlTag(key: string): boolean;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List of all HTML tags used to identify selectors
|
|
3
|
+
*/
|
|
4
|
+
export const htmlTags = [
|
|
5
|
+
'a',
|
|
6
|
+
'abbr',
|
|
7
|
+
'address',
|
|
8
|
+
'area',
|
|
9
|
+
'article',
|
|
10
|
+
'aside',
|
|
11
|
+
'audio',
|
|
12
|
+
'b',
|
|
13
|
+
'base',
|
|
14
|
+
'bdi',
|
|
15
|
+
'bdo',
|
|
16
|
+
'blockquote',
|
|
17
|
+
'body',
|
|
18
|
+
'br',
|
|
19
|
+
'button',
|
|
20
|
+
'canvas',
|
|
21
|
+
'caption',
|
|
22
|
+
'cite',
|
|
23
|
+
'code',
|
|
24
|
+
'col',
|
|
25
|
+
'colgroup',
|
|
26
|
+
'data',
|
|
27
|
+
'datalist',
|
|
28
|
+
'dd',
|
|
29
|
+
'del',
|
|
30
|
+
'details',
|
|
31
|
+
'dfn',
|
|
32
|
+
'dialog',
|
|
33
|
+
'div',
|
|
34
|
+
'dl',
|
|
35
|
+
'dt',
|
|
36
|
+
'em',
|
|
37
|
+
'embed',
|
|
38
|
+
'fieldset',
|
|
39
|
+
'figcaption',
|
|
40
|
+
'figure',
|
|
41
|
+
'footer',
|
|
42
|
+
'form',
|
|
43
|
+
'h1',
|
|
44
|
+
'h2',
|
|
45
|
+
'h3',
|
|
46
|
+
'h4',
|
|
47
|
+
'h5',
|
|
48
|
+
'h6',
|
|
49
|
+
'head',
|
|
50
|
+
'header',
|
|
51
|
+
'hgroup',
|
|
52
|
+
'hr',
|
|
53
|
+
'html',
|
|
54
|
+
'i',
|
|
55
|
+
'iframe',
|
|
56
|
+
'img',
|
|
57
|
+
'input',
|
|
58
|
+
'ins',
|
|
59
|
+
'kbd',
|
|
60
|
+
'label',
|
|
61
|
+
'legend',
|
|
62
|
+
'li',
|
|
63
|
+
'link',
|
|
64
|
+
'main',
|
|
65
|
+
'map',
|
|
66
|
+
'mark',
|
|
67
|
+
'meta',
|
|
68
|
+
'meter',
|
|
69
|
+
'nav',
|
|
70
|
+
'noscript',
|
|
71
|
+
'object',
|
|
72
|
+
'ol',
|
|
73
|
+
'optgroup',
|
|
74
|
+
'option',
|
|
75
|
+
'output',
|
|
76
|
+
'p',
|
|
77
|
+
'param',
|
|
78
|
+
'picture',
|
|
79
|
+
'pre',
|
|
80
|
+
'progress',
|
|
81
|
+
'q',
|
|
82
|
+
'rp',
|
|
83
|
+
'rt',
|
|
84
|
+
'ruby',
|
|
85
|
+
's',
|
|
86
|
+
'samp',
|
|
87
|
+
'script',
|
|
88
|
+
'section',
|
|
89
|
+
'select',
|
|
90
|
+
'slot',
|
|
91
|
+
'small',
|
|
92
|
+
'source',
|
|
93
|
+
'span',
|
|
94
|
+
'strong',
|
|
95
|
+
'style',
|
|
96
|
+
'sub',
|
|
97
|
+
'summary',
|
|
98
|
+
'sup',
|
|
99
|
+
'svg',
|
|
100
|
+
'table',
|
|
101
|
+
'tbody',
|
|
102
|
+
'td',
|
|
103
|
+
'template',
|
|
104
|
+
'textarea',
|
|
105
|
+
'tfoot',
|
|
106
|
+
'th',
|
|
107
|
+
'thead',
|
|
108
|
+
'time',
|
|
109
|
+
'title',
|
|
110
|
+
'tr',
|
|
111
|
+
'track',
|
|
112
|
+
'u',
|
|
113
|
+
'ul',
|
|
114
|
+
'var',
|
|
115
|
+
'video',
|
|
116
|
+
'wbr',
|
|
117
|
+
];
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a string is an HTML tag name
|
|
120
|
+
* @param key - String to check
|
|
121
|
+
* @returns True if the string is an HTML tag name
|
|
122
|
+
*/
|
|
123
|
+
export function isHtmlTag(key) {
|
|
124
|
+
if (/^\w+[.#:[~+ ]/.test(key)) {
|
|
125
|
+
return htmlTags.includes(key.split(/[.#:[~+ ]/)[0]);
|
|
126
|
+
}
|
|
127
|
+
return htmlTags.includes(key);
|
|
128
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for traversing and transforming nested objects
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Type for node visitor functions
|
|
6
|
+
*/
|
|
7
|
+
export type NodeVisitor<T> = (node: any, path: string, root: any, index: number) => T;
|
|
8
|
+
/**
|
|
9
|
+
* Traverses a nested object and applies a visitor function to each node
|
|
10
|
+
* @param node - The object to traverse
|
|
11
|
+
* @param visitor - Function to call for each node
|
|
12
|
+
* @param path - Current path in the object (for tracking)
|
|
13
|
+
* @param root - Root object (for reference)
|
|
14
|
+
* @param index - Current index in array (if applicable)
|
|
15
|
+
* @param separator - Path separator character
|
|
16
|
+
* @returns The result of the visitor function on the current node
|
|
17
|
+
*/
|
|
18
|
+
export declare function traverseObject<T>(node: any, visitor: NodeVisitor<T>, path?: string, root?: any, index?: number, separator?: string): T;
|
|
19
|
+
/**
|
|
20
|
+
* Determines the type of a node in the CSS object
|
|
21
|
+
* @param node - The node to check
|
|
22
|
+
* @param path - Current path in the object
|
|
23
|
+
* @returns The identified node type as a string
|
|
24
|
+
*/
|
|
25
|
+
export declare function determineNodeType(node: any, path?: string): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for traversing and transforming nested objects
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Traverses a nested object and applies a visitor function to each node
|
|
6
|
+
* @param node - The object to traverse
|
|
7
|
+
* @param visitor - Function to call for each node
|
|
8
|
+
* @param path - Current path in the object (for tracking)
|
|
9
|
+
* @param root - Root object (for reference)
|
|
10
|
+
* @param index - Current index in array (if applicable)
|
|
11
|
+
* @param separator - Path separator character
|
|
12
|
+
* @returns The result of the visitor function on the current node
|
|
13
|
+
*/
|
|
14
|
+
export function traverseObject(node, visitor, path = '', root, index = -1, separator = '\\') {
|
|
15
|
+
const realRoot = root || node;
|
|
16
|
+
// Handle arrays
|
|
17
|
+
if (Array.isArray(node)) {
|
|
18
|
+
const processedItems = node.map((item, idx) => traverseObject(item, visitor, path, realRoot, idx, separator));
|
|
19
|
+
return visitor(processedItems, path, realRoot, index);
|
|
20
|
+
}
|
|
21
|
+
// Handle objects (but not null)
|
|
22
|
+
if (node !== null && typeof node === 'object') {
|
|
23
|
+
const processedObject = Object.keys(node).reduce((result, key) => {
|
|
24
|
+
const newPath = path ? `${path}${separator}${key}` : key;
|
|
25
|
+
const processedValue = traverseObject(node[key], visitor, newPath, realRoot, index, separator);
|
|
26
|
+
result[key] = processedValue;
|
|
27
|
+
return result;
|
|
28
|
+
}, {});
|
|
29
|
+
return visitor(processedObject, path, realRoot, index);
|
|
30
|
+
}
|
|
31
|
+
// Handle primitive values
|
|
32
|
+
return visitor(node, path, realRoot, index);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Determines the type of a node in the CSS object
|
|
36
|
+
* @param node - The node to check
|
|
37
|
+
* @param path - Current path in the object
|
|
38
|
+
* @returns The identified node type as a string
|
|
39
|
+
*/
|
|
40
|
+
export function determineNodeType(node, path) {
|
|
41
|
+
if (!path)
|
|
42
|
+
return 'unknown';
|
|
43
|
+
const lastKey = path.split('\\').pop() || '';
|
|
44
|
+
// Layer statement
|
|
45
|
+
if (/^@layer/.test(lastKey) && typeof node === 'string') {
|
|
46
|
+
return 'layer statement';
|
|
47
|
+
}
|
|
48
|
+
// Layer block
|
|
49
|
+
if (/^@layer/.test(lastKey) && typeof node === 'object' && node !== null) {
|
|
50
|
+
return 'layer block';
|
|
51
|
+
}
|
|
52
|
+
// Container query
|
|
53
|
+
if (/^@container/.test(lastKey) &&
|
|
54
|
+
typeof node === 'object' &&
|
|
55
|
+
node !== null) {
|
|
56
|
+
return 'container query block';
|
|
57
|
+
}
|
|
58
|
+
// Media query or prefix
|
|
59
|
+
if (/^@/.test(lastKey)) {
|
|
60
|
+
return 'media query or prefix';
|
|
61
|
+
}
|
|
62
|
+
// CSS property-value pair (end value)
|
|
63
|
+
if (isPrimitiveValue(node)) {
|
|
64
|
+
return 'selector';
|
|
65
|
+
}
|
|
66
|
+
return 'unknown';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a value is a primitive that can be used as a CSS property value
|
|
70
|
+
* @param value - Value to check
|
|
71
|
+
* @returns True if the value is a primitive
|
|
72
|
+
*/
|
|
73
|
+
function isPrimitiveValue(value) {
|
|
74
|
+
return (value === null ||
|
|
75
|
+
typeof value === 'string' ||
|
|
76
|
+
typeof value === 'number' ||
|
|
77
|
+
typeof value === 'boolean');
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "esm-styles",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A library for working with ESM styles",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"lint": "eslint src --ext .ts",
|
|
15
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
16
|
+
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
17
|
+
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"esm",
|
|
22
|
+
"styles",
|
|
23
|
+
"css"
|
|
24
|
+
],
|
|
25
|
+
"author": "romochka@gmail.com",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=14.16"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/jest": "^29.5.11",
|
|
32
|
+
"@types/node": "^20.11.0",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
34
|
+
"@typescript-eslint/parser": "^6.18.1",
|
|
35
|
+
"eslint": "^8.56.0",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"ts-jest": "^29.1.1",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
|
+
}
|
|
40
|
+
}
|