esm-styles 0.1.11 → 0.1.13

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 CHANGED
@@ -1,6 +1,15 @@
1
1
  # ESM Styles
2
2
 
3
- A TypeScript library for converting JavaScript objects to CSS strings, allowing for a cleaner syntax when writing CSS-in-JS.
3
+ A CSS-in-JS solution for JavaScript/TypeScript projects.
4
+
5
+ ## Features
6
+
7
+ - JavaScript to CSS conversion with an intuitive object syntax
8
+ - Build CSS from organized source files with a simple CLI
9
+ - CSS layering support for proper style encapsulation
10
+ - Media query and device/theme selectors with shorthands
11
+ - CSS variables with inheritance between themes
12
+ - Supporting modules for easy CSS variable usage
4
13
 
5
14
  ## Installation
6
15
 
@@ -10,146 +19,386 @@ npm install esm-styles
10
19
 
11
20
  ## Usage
12
21
 
13
- ### Basic Usage
22
+ ### Basic Concept
14
23
 
15
- ```javascript
16
- import { getCss } from 'esm-styles'
24
+ ESM Styles lets you write CSS in JavaScript objects with a natural syntax that converts to proper CSS:
17
25
 
18
- const styles = {
19
- body: {
20
- margin: 0,
21
- padding: 0,
22
- fontFamily: 'sans-serif',
26
+ ```js
27
+ // component.styles.mjs
28
+ export default {
29
+ button: {
30
+ backgroundColor: '#4285f4',
31
+ color: 'white',
32
+ padding: '10px 20px',
33
+ borderRadius: '4px',
23
34
 
24
- header: {
25
- backgroundColor: '#333',
26
- color: 'white',
27
- padding: '1rem',
35
+ ':hover': {
36
+ backgroundColor: '#3367d6',
28
37
  },
29
38
 
30
- 'main, article': {
31
- maxWidth: '800px',
32
- margin: '0 auto',
33
- padding: '1rem',
39
+ '@media (max-width: 768px)': {
40
+ padding: '8px 16px',
34
41
  },
42
+ },
43
+ }
44
+ ```
45
+
46
+ ### CLI Usage
47
+
48
+ Build your styles by creating a configuration file and running the CLI:
49
+
50
+ ```bash
51
+ npx build
52
+ ```
53
+
54
+ Or specify a custom config:
55
+
56
+ ```bash
57
+ npx build path/to/config.js
58
+ ```
59
+
60
+ Watch for changes:
61
+
62
+ ```bash
63
+ npx watch
64
+ ```
65
+
66
+ ### Configuration
67
+
68
+ Create a `esm-styles.config.js` in your project root (or use a custom path):
69
+
70
+ ```js
71
+ export default {
72
+ basePath: './src/styles',
73
+ sourcePath: 'source',
74
+ outputPath: 'css',
75
+ sourceFilesSuffix: '.styles.mjs',
76
+
77
+ // Input layers
78
+ layers: ['defaults', 'components', 'layout'],
79
+
80
+ // Output
81
+ mainCssFile: 'styles.css',
82
+
83
+ // Global variables
84
+ globalVariables: 'global',
85
+ globalRootSelector: ':root',
86
+
87
+ // Media types and queries
88
+ media: {
89
+ theme: ['light', 'dark'],
90
+ device: ['mobile', 'tablet', 'desktop'],
91
+ },
35
92
 
36
- footer: {
37
- textAlign: 'center',
38
- padding: '1rem',
39
- backgroundColor: '#f5f5f5',
93
+ mediaSelectors: {
94
+ theme: {
95
+ light: [
96
+ {
97
+ selector: '.light',
98
+ },
99
+ {
100
+ selector: '.auto',
101
+ mediaQuery: 'screen and (prefers-color-scheme: light)',
102
+ prefix: 'auto',
103
+ },
104
+ ],
105
+ dark: [
106
+ {
107
+ selector: '.dark',
108
+ },
109
+ {
110
+ selector: '.auto',
111
+ mediaQuery: 'screen and (prefers-color-scheme: dark)',
112
+ prefix: 'auto',
113
+ },
114
+ ],
115
+ },
116
+ // Device selectors
117
+ device: {
118
+ mobile: [
119
+ {
120
+ mediaQuery: 'screen and (max-width: 767px)',
121
+ },
122
+ ],
123
+ tablet: [
124
+ {
125
+ mediaQuery: 'screen and (min-width: 768px) and (max-width: 1024px)',
126
+ },
127
+ ],
128
+ desktop: [
129
+ {
130
+ mediaQuery: 'screen and (min-width: 1025px)',
131
+ },
132
+ ],
40
133
  },
41
134
  },
135
+
136
+ // Media query shorthands
137
+ mediaQueries: {
138
+ mobile: '(max-width: 767px)',
139
+ tablet: '(min-width: 768px) and (max-width: 1024px)',
140
+ desktop: '(min-width: 1025px)',
141
+ },
42
142
  }
143
+ ```
144
+
145
+ ## JS to CSS Translation
146
+
147
+ ### Basic Selectors
43
148
 
44
- const css = getCss(styles)
45
- console.log(css)
149
+ ```js
150
+ {
151
+ p: {
152
+ fontSize: '16px',
153
+ color: 'black',
154
+
155
+ a: {
156
+ color: 'blue'
157
+ },
158
+
159
+ strong: {
160
+ fontWeight: 'bold'
161
+ }
162
+ }
163
+ }
46
164
  ```
47
165
 
48
- This will output CSS with nested selectors properly expanded:
166
+ Compiles to:
49
167
 
50
168
  ```css
51
- body {
52
- margin: 0;
53
- padding: 0;
54
- font-family: sans-serif;
169
+ p {
170
+ font-size: 16px;
171
+ color: black;
172
+ }
173
+
174
+ p a {
175
+ color: blue;
55
176
  }
56
177
 
57
- body header {
58
- background-color: #333;
59
- color: white;
60
- padding: 1rem;
178
+ p strong {
179
+ font-weight: bold;
61
180
  }
181
+ ```
62
182
 
63
- body main,
64
- body article {
65
- max-width: 800px;
66
- margin: 0 auto;
67
- padding: 1rem;
183
+ ### Class Selectors
184
+
185
+ ```js
186
+ {
187
+ // Class selectors for non-HTML tag names
188
+ header: {
189
+ backgroundColor: '#f5f5f5',
190
+ padding: '20px'
191
+ },
192
+
193
+ // Class on HTML tag using underscore prefix
194
+ p: {
195
+ _highlight: {
196
+ backgroundColor: 'yellow'
197
+ }
198
+ }
68
199
  }
200
+ ```
69
201
 
70
- body footer {
71
- text-align: center;
72
- padding: 1rem;
202
+ Compiles to:
203
+
204
+ ```css
205
+ .header {
73
206
  background-color: #f5f5f5;
207
+ padding: 20px;
208
+ }
209
+
210
+ p.highlight {
211
+ background-color: yellow;
212
+ }
213
+ ```
214
+
215
+ ### Double Underscore for Descendant Class Selector
216
+
217
+ ```js
218
+ {
219
+ modal: {
220
+ position: 'relative',
221
+
222
+ __close: {
223
+ position: 'absolute',
224
+ top: '10px',
225
+ right: '10px'
226
+ }
227
+ }
74
228
  }
75
229
  ```
76
230
 
77
- ### Advanced Features
231
+ Compiles to:
78
232
 
79
- #### Media Queries
233
+ ```css
234
+ .modal {
235
+ position: relative;
236
+ }
80
237
 
81
- ```javascript
82
- import { getCss } from 'esm-styles'
238
+ .modal .close {
239
+ position: absolute;
240
+ top: 10px;
241
+ right: 10px;
242
+ }
243
+ ```
83
244
 
84
- const styles = {
85
- body: {
86
- fontSize: '16px',
245
+ ### Multiple Selectors
87
246
 
88
- '@media (max-width: 768px)': {
89
- fontSize: '14px',
90
- },
247
+ ```js
248
+ {
249
+ 'button, .btn': {
250
+ padding: '10px 20px'
91
251
  },
252
+
253
+ 'input[type="text"], input[type="email"]': {
254
+ borderRadius: '4px'
255
+ }
92
256
  }
257
+ ```
258
+
259
+ ### Nested Media Queries
93
260
 
94
- const css = getCss(styles)
261
+ ```js
262
+ {
263
+ card: {
264
+ display: 'flex',
265
+
266
+ '@media (max-width: 768px)': {
267
+ flexDirection: 'column',
268
+
269
+ '@media (orientation: portrait)': {
270
+ padding: '10px'
271
+ }
272
+ }
273
+ }
274
+ }
95
275
  ```
96
276
 
97
- #### Named Media Queries
277
+ ### Named Media Queries
98
278
 
99
- ```javascript
100
- import { getCss } from 'esm-styles'
279
+ ```js
280
+ {
281
+ main: {
282
+ display: 'grid',
101
283
 
102
- const styles = {
103
- container: {
104
- width: '1200px',
105
- '@tablet': {
106
- width: '100%',
107
- padding: '0 20px',
108
- },
109
284
  '@mobile': {
110
- padding: '0 10px',
285
+ display: 'flex',
286
+ flexDirection: 'column'
111
287
  },
112
- },
288
+
289
+ '@desktop': {
290
+ gridTemplateColumns: 'repeat(3, 1fr)'
291
+ }
292
+ }
113
293
  }
294
+ ```
295
+
296
+ ### Theme Support
297
+
298
+ ```js
299
+ {
300
+ card: {
301
+ backgroundColor: 'white',
302
+ color: 'black',
114
303
 
115
- const mediaQueries = {
116
- tablet: '(max-width: 1024px)',
117
- mobile: '(max-width: 480px)',
304
+ '@dark': {
305
+ backgroundColor: '#222',
306
+ color: 'white'
307
+ }
308
+ }
118
309
  }
310
+ ```
311
+
312
+ ### CSS Variables
313
+
314
+ Define variables in a global variables file:
119
315
 
120
- const css = getCss(styles, mediaQueries)
316
+ ```js
317
+ // global.styles.mjs
318
+ export default {
319
+ colors: {
320
+ primary: '#4285f4',
321
+ secondary: '#34a853',
322
+ error: '#ea4335',
323
+ },
324
+ spacing: {
325
+ sm: '8px',
326
+ md: '16px',
327
+ lg: '24px',
328
+ },
329
+ }
121
330
  ```
122
331
 
123
- #### Class and Tag Selectors
332
+ Define theme-specific variables:
124
333
 
125
- ```javascript
126
- import { getCss } from 'esm-styles'
334
+ ```js
335
+ // light.styles.mjs
336
+ export default {
337
+ paper: {
338
+ bright: '#ffffff',
339
+ tinted: '#f0f0f0',
340
+ },
341
+ ink: {
342
+ bright: '#000000',
343
+ faded: '#333333',
344
+ accent: '#ff0000',
345
+ },
346
+ }
347
+ ```
127
348
 
128
- const styles = {
129
- // Tag selector (div)
130
- div: {
131
- margin: '10px',
349
+ ```js
350
+ // dark.styles.mjs
351
+ export default {
352
+ paper: {
353
+ bright: '#000000',
354
+ tinted: '#323232',
355
+ },
356
+ ink: {
357
+ bright: '#ffffff',
358
+ faded: '#b3b3b3',
359
+ },
360
+ }
361
+ ```
132
362
 
133
- // Nested tag selector (p inside div)
134
- p: {
135
- lineHeight: 1.5,
136
- },
363
+ Use with supporting modules:
137
364
 
138
- // Class selector (with underscore prefix)
139
- _highlight: {
140
- backgroundColor: 'yellow',
141
- },
365
+ ```js
366
+ // component.styles.mjs
367
+ import $theme from './$theme.mjs'
142
368
 
143
- // Descendant class selector (with double underscore)
144
- __text: {
145
- color: 'blue',
146
- },
369
+ export default {
370
+ button: {
371
+ backgroundColor: $theme.paper.bright,
372
+ color: $theme.ink.bright,
373
+ padding: '10px 20px',
147
374
  },
148
375
  }
376
+ ```
377
+
378
+ ## Advanced Features
149
379
 
150
- const css = getCss(styles)
380
+ ### Layering
381
+
382
+ Organize your styles in layers for better control over specificity:
383
+
384
+ ```js
385
+ // defaults.styles.mjs, components.styles.mjs, layout.styles.mjs
151
386
  ```
152
387
 
388
+ The build process wraps each in an appropriate layer and generates a main CSS file with proper import order.
389
+
390
+ ### CSS Variable Inheritance
391
+
392
+ Missing variables in one theme automatically inherit from the previous theme in the configuration.
393
+
394
+ ## Additional documentation
395
+
396
+ For humans: [doc/usage-guide.md](doc/usage-guide.md)
397
+
398
+ For AI assistants: [doc/ai-guide.md](doc/ai-guide.md)
399
+
400
+ API reference: [doc/api-reference.md](doc/api-reference.md)
401
+
153
402
  ## License
154
403
 
155
404
  MIT
package/dist/lib/build.js CHANGED
@@ -4,7 +4,6 @@ import { getMediaShorthands } from './utils/media-shorthand.js';
4
4
  import { isEndValue } from './utils/index.js';
5
5
  import path from 'node:path';
6
6
  import fs from 'node:fs/promises';
7
- import { inspect } from 'node:util';
8
7
  import _ from 'lodash';
9
8
  export async function build(configPath = 'esm-styles.config.js') {
10
9
  // --- Supporting module generation ---
@@ -86,8 +85,8 @@ export async function build(configPath = 'esm-styles.config.js') {
86
85
  }
87
86
  // --- Supporting module generation ---
88
87
  // Debug: log mergedSets and sets
89
- console.log('[DEBUG] sets:', sets);
90
- console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }));
88
+ // console.log('[DEBUG] sets:', sets)
89
+ // console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }))
91
90
  // Define the recursive function here so it has access to sets and mergedSets
92
91
  const buildSupportingModule = (path, isRoot) => {
93
92
  const allKeys = new Set();
@@ -101,11 +100,24 @@ export async function build(configPath = 'esm-styles.config.js') {
101
100
  }
102
101
  }
103
102
  // Debug: show allKeys and values at this path
104
- console.log('[DEBUG] path:', path.join('.'), 'allKeys:', Array.from(allKeys));
105
- for (const set of sets) {
106
- const v = path.length === 0 ? mergedSets[set] : _.get(mergedSets[set], path);
107
- console.log('[DEBUG] set:', set, 'path:', path.join('.'), 'value:', JSON.stringify(v));
108
- }
103
+ // console.log(
104
+ // '[DEBUG] path:',
105
+ // path.join('.'),
106
+ // 'allKeys:',
107
+ // Array.from(allKeys)
108
+ // )
109
+ // for (const set of sets) {
110
+ // const v =
111
+ // path.length === 0 ? mergedSets[set] : _.get(mergedSets[set], path)
112
+ // console.log(
113
+ // '[DEBUG] set:',
114
+ // set,
115
+ // 'path:',
116
+ // path.join('.'),
117
+ // 'value:',
118
+ // JSON.stringify(v)
119
+ // )
120
+ // }
109
121
  const result = {};
110
122
  for (const key of allKeys) {
111
123
  if (key === '') {
@@ -132,7 +144,12 @@ export async function build(configPath = 'esm-styles.config.js') {
132
144
  }
133
145
  }
134
146
  // Debug log for each recursion
135
- console.log('[DEBUG] path:', path.join('.'), 'result:', JSON.stringify(result, null, 2));
147
+ // console.log(
148
+ // '[DEBUG] path:',
149
+ // path.join('.'),
150
+ // 'result:',
151
+ // JSON.stringify(result, null, 2)
152
+ // )
136
153
  return result;
137
154
  };
138
155
  const supportingModuleObj = buildSupportingModule([], true);
@@ -1,4 +1,15 @@
1
1
  import * as utils from './cartesian.js';
2
+ const svgTags = [
3
+ 'circle',
4
+ 'ellipse',
5
+ 'g',
6
+ 'line',
7
+ 'polygon',
8
+ 'polyline',
9
+ 'path',
10
+ 'rect',
11
+ 'text',
12
+ ];
2
13
  // List of standard HTML tags (not exhaustive, but covers common cases)
3
14
  const HTML_TAGS = new Set([
4
15
  'html',
@@ -135,6 +146,7 @@ const HTML_TAGS = new Set([
135
146
  'strike',
136
147
  'tt',
137
148
  'xmp',
149
+ ...svgTags,
138
150
  ]);
139
151
  const isHtmlTag = (key) => {
140
152
  // Only match if the key is a plain tag name (no underscores, no special chars)