esm-styles 0.2.2 → 0.2.4

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
@@ -185,16 +185,18 @@ p strong {
185
185
 
186
186
  ```js
187
187
  {
188
- // Class selectors for non-HTML tag names
189
- header: {
190
- backgroundColor: '#f5f5f5',
191
- padding: '20px'
192
- },
193
-
194
- // Class on HTML tag using underscore prefix
195
- p: {
196
- _highlight: {
197
- backgroundColor: 'yellow'
188
+ div: {
189
+ highlighted: {
190
+ // highlighted is not a tag
191
+ border: '1px solid red',
192
+ },
193
+ p: {
194
+ // p is a tag
195
+ fontSize: '16px',
196
+ },
197
+ _video: {
198
+ // video is a tag, but the class is meant, use single underscore prefix
199
+ aspectRatio: 1.77,
198
200
  }
199
201
  }
200
202
  }
@@ -203,13 +205,16 @@ p strong {
203
205
  Compiles to:
204
206
 
205
207
  ```css
206
- .header {
207
- background-color: #f5f5f5;
208
- padding: 20px;
208
+ div.highlighted {
209
+ border: 1px solid red;
210
+ }
211
+
212
+ div p {
213
+ font-size: 16px;
209
214
  }
210
215
 
211
- p.highlight {
212
- background-color: yellow;
216
+ div.video {
217
+ aspect-ratio: 1.77;
213
218
  }
214
219
  ```
215
220
 
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as utils from './utils/index.js';
2
2
  import { toCssValue } from './utils/to-css-value.js';
3
- function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layers: {} }, options = {}, currentMedia = []) {
3
+ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layers: {}, containers: {} }, options = {}, currentMedia = [], currentContainers = []) {
4
4
  const props = {};
5
5
  for (const key in obj) {
6
6
  const value = obj[key];
@@ -16,7 +16,7 @@ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layer
16
16
  else if (typeof value === 'object' && value !== null) {
17
17
  if (key.startsWith('@media ')) {
18
18
  // Recursively walk value, collecting rules for this media block
19
- const nested = flatWalk(value, selectorPath, { rules: [], media: {}, layers: {} }, options, [...currentMedia, key]);
19
+ const nested = flatWalk(value, selectorPath, { rules: [], media: {}, layers: {}, containers: {} }, options, [...currentMedia, key], currentContainers);
20
20
  const mediaKey = [...currentMedia, key].join(' && ');
21
21
  if (!result.media[mediaKey])
22
22
  result.media[mediaKey] = [];
@@ -27,11 +27,42 @@ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layer
27
27
  result.media[nestedMediaKey] = [];
28
28
  result.media[nestedMediaKey].push(...nested.media[nestedMediaKey]);
29
29
  }
30
+ // Merge nested containers
31
+ for (const nestedContainerKey in nested.containers) {
32
+ if (!result.containers[nestedContainerKey])
33
+ result.containers[nestedContainerKey] = [];
34
+ result.containers[nestedContainerKey].push(...nested.containers[nestedContainerKey]);
35
+ }
36
+ }
37
+ else if (key.startsWith('@container')) {
38
+ // Recursively walk value, collecting rules for this container block
39
+ const nested = flatWalk(value, selectorPath, { rules: [], media: {}, layers: {}, containers: {} }, options, currentMedia, [...currentContainers, key]);
40
+ const containerKey = [...currentContainers, key].join(' && ');
41
+ if (!result.containers[containerKey])
42
+ result.containers[containerKey] = [];
43
+ result.containers[containerKey].push(...nested.rules);
44
+ // Also merge any nested containers
45
+ for (const nestedContainerKey in nested.containers) {
46
+ if (!result.containers[nestedContainerKey])
47
+ result.containers[nestedContainerKey] = [];
48
+ result.containers[nestedContainerKey].push(...nested.containers[nestedContainerKey]);
49
+ }
50
+ // Merge nested media
51
+ for (const nestedMediaKey in nested.media) {
52
+ if (!result.media[nestedMediaKey])
53
+ result.media[nestedMediaKey] = [];
54
+ result.media[nestedMediaKey].push(...nested.media[nestedMediaKey]);
55
+ }
30
56
  }
31
57
  else if (key.startsWith('@layer ')) {
32
58
  if (!result.layers[key])
33
- result.layers[key] = { rules: [], media: {}, layers: {} };
34
- flatWalk(value, selectorPath, result.layers[key], options, currentMedia);
59
+ result.layers[key] = {
60
+ rules: [],
61
+ media: {},
62
+ layers: {},
63
+ containers: {},
64
+ };
65
+ flatWalk(value, selectorPath, result.layers[key], options, currentMedia, currentContainers);
35
66
  }
36
67
  else if (key.startsWith('@')) {
37
68
  // Other special keys (shorthands, etc.)
@@ -39,10 +70,7 @@ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layer
39
70
  let handled = false;
40
71
  if (options.mediaQueries && options.mediaQueries[shorthand]) {
41
72
  const mediaKey = `@media ${options.mediaQueries[shorthand]}`;
42
- flatWalk(value, selectorPath, result, options, [
43
- ...currentMedia,
44
- mediaKey,
45
- ]);
73
+ flatWalk(value, selectorPath, result, options, [...currentMedia, mediaKey], currentContainers);
46
74
  handled = true;
47
75
  }
48
76
  if (options.selectorShorthands &&
@@ -50,20 +78,17 @@ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layer
50
78
  const root = options.globalRootSelector || ':root';
51
79
  for (const entry of options.selectorShorthands[shorthand]) {
52
80
  if (entry.selector && !entry.mediaQuery) {
53
- flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, currentMedia);
81
+ flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, currentMedia, currentContainers);
54
82
  handled = true;
55
83
  }
56
84
  if (entry.selector && entry.mediaQuery) {
57
85
  const mediaKey = `@media ${entry.mediaQuery}`;
58
- flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, [...currentMedia, mediaKey]);
86
+ flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, [...currentMedia, mediaKey], currentContainers);
59
87
  handled = true;
60
88
  }
61
89
  if (!entry.selector && entry.mediaQuery) {
62
90
  const mediaKey = `@media ${entry.mediaQuery}`;
63
- flatWalk(value, selectorPath, result, options, [
64
- ...currentMedia,
65
- mediaKey,
66
- ]);
91
+ flatWalk(value, selectorPath, result, options, [...currentMedia, mediaKey], currentContainers);
67
92
  handled = true;
68
93
  }
69
94
  }
@@ -74,14 +99,14 @@ function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layer
74
99
  }
75
100
  else {
76
101
  const parts = key.split(',').map((k) => k.trim());
77
- flatWalk(value, [...selectorPath, parts], result, options, currentMedia);
102
+ flatWalk(value, [...selectorPath, parts], result, options, currentMedia, currentContainers);
78
103
  }
79
104
  }
80
105
  }
81
106
  else {
82
107
  // Always treat as selector segment if not a special key
83
108
  const parts = key.split(',').map((k) => k.trim());
84
- flatWalk(value, [...selectorPath, parts], result, options, currentMedia);
109
+ flatWalk(value, [...selectorPath, parts], result, options, currentMedia, currentContainers);
85
110
  }
86
111
  }
87
112
  }
@@ -184,6 +209,58 @@ function renderFlatMedia(media) {
184
209
  }
185
210
  return css;
186
211
  }
212
+ function renderFlatContainers(containers) {
213
+ let css = '';
214
+ for (const key in containers) {
215
+ // If the key contains '&&', recursively nest the container blocks
216
+ const containerParts = key.split(' && ');
217
+ if (containerParts.length > 1) {
218
+ css += containerParts.reduceRight((inner, part) => `${part} {\n${inner}\n}`, renderFlatContainers({ ['']: containers[key] }));
219
+ }
220
+ else if (key === '') {
221
+ // Render rules directly, no block
222
+ // Group rules by their declarations (stringified)
223
+ const groups = {};
224
+ for (const rule of containers[key]) {
225
+ const declKey = JSON.stringify(rule.declarations);
226
+ if (!groups[declKey])
227
+ groups[declKey] = [];
228
+ groups[declKey].push(rule.selector);
229
+ }
230
+ for (const declKey in groups) {
231
+ const selectors = groups[declKey].join(', ');
232
+ const declarations = JSON.parse(declKey);
233
+ css += `${selectors} {\n`;
234
+ for (const k in declarations) {
235
+ css += ` ${k}: ${declarations[k]};\n`;
236
+ }
237
+ css += '}\n';
238
+ }
239
+ }
240
+ else {
241
+ css += `${key} {\n`;
242
+ // Group rules by their declarations (stringified)
243
+ const groups = {};
244
+ for (const rule of containers[key]) {
245
+ const declKey = JSON.stringify(rule.declarations);
246
+ if (!groups[declKey])
247
+ groups[declKey] = [];
248
+ groups[declKey].push(rule.selector);
249
+ }
250
+ for (const declKey in groups) {
251
+ const selectors = groups[declKey].join(', ');
252
+ const declarations = JSON.parse(declKey);
253
+ css += `${selectors} {\n`;
254
+ for (const k in declarations) {
255
+ css += ` ${k}: ${declarations[k]};\n`;
256
+ }
257
+ css += '}\n';
258
+ }
259
+ css += '}\n';
260
+ }
261
+ }
262
+ return css;
263
+ }
187
264
  function mergeMedia(target, source) {
188
265
  for (const key in source) {
189
266
  if (!target[key])
@@ -192,7 +269,7 @@ function mergeMedia(target, source) {
192
269
  }
193
270
  }
194
271
  export function getCss(styles, options = {}) {
195
- const result = flatWalk(styles, [], { rules: [], media: {}, layers: {} }, options);
272
+ const result = flatWalk(styles, [], { rules: [], media: {}, layers: {}, containers: {} }, options);
196
273
  if (typeof console !== 'undefined') {
197
274
  // console.log(
198
275
  // '[esm-styles] flatWalk result:',
@@ -202,6 +279,7 @@ export function getCss(styles, options = {}) {
202
279
  let css = '';
203
280
  css += renderRules(result.rules);
204
281
  css += renderFlatMedia(result.media);
282
+ css += renderFlatContainers(result.containers);
205
283
  css += renderLayers(result.layers);
206
284
  return utils.prettifyCssString(css);
207
285
  }
@@ -195,7 +195,16 @@ export const joinSelectorPath = (path) => {
195
195
  return acc + (acc ? ' ' : '') + '.' + part.slice(2);
196
196
  }
197
197
  else if (part.startsWith('_')) {
198
- return acc + (acc ? ' ' : '') + '.' + part.slice(1);
198
+ // Attach class directly to previous part unless prev is combinator or root
199
+ const combinators = ['>', '+', '~'];
200
+ const isPrevCombinator = prev && combinators.some((c) => prev.startsWith(c));
201
+ if (isPrevRoot || isPrevCombinator || !acc) {
202
+ return acc + (acc ? ' ' : '') + '.' + part.slice(1);
203
+ }
204
+ else {
205
+ // Attach directly (no space)
206
+ return acc + '.' + part.slice(1);
207
+ }
199
208
  }
200
209
  else if (part.startsWith('>') ||
201
210
  part.startsWith('+') ||
@@ -224,6 +233,13 @@ export const joinSelectorPath = (path) => {
224
233
  return acc + (acc ? ' ' : '') + match[1] + '.' + match[2];
225
234
  }
226
235
  }
236
+ else if (/^([a-z][a-z0-9]*)#([\w-]+)$/.test(part)) {
237
+ // If part matches 'tag#id' and tag is an HTML tag
238
+ const match = part.match(/^([a-z][a-z0-9]*)#([\w-]+)$/);
239
+ if (match && isHtmlTag(match[1])) {
240
+ return acc + (acc ? ' ' : '') + match[1] + '#' + match[2];
241
+ }
242
+ }
227
243
  // Not a tag, not a special selector: treat as class or custom element
228
244
  // If previous part is a root selector, insert a space
229
245
  if (isPrevRoot) {
@@ -307,21 +307,9 @@ Use commas to target multiple selectors:
307
307
  }
308
308
  ```
309
309
 
310
- ### T9: Content Property
311
-
312
- For the `content` property, use JavaScript unicode notation for special characters:
313
-
314
- ```js
315
- {
316
- '::before': {
317
- content: '\u2022', // Bullet character
318
- }
319
- }
320
- ```
321
-
322
310
  ## Layers
323
311
 
324
- CSS layers help manage specificity and provide better organization of styles. ESM Styles supports @layer directives:
312
+ ESM Styles supports @layer directives:
325
313
 
326
314
  ```js
327
315
  {
@@ -551,12 +539,14 @@ The build process automatically generates modules like `$theme.mjs`:
551
539
  export default {
552
540
  colors: {
553
541
  background: {
554
- var: '--colors-background',
542
+ var: 'var(--colors-background)',
543
+ name: '--colors-background',
555
544
  light: '#ffffff',
556
545
  dark: '#121212',
557
546
  },
558
547
  surface: {
559
- var: '--colors-surface',
548
+ var: 'var(--colors-surface)',
549
+ name: '--colors-surface',
560
550
  light: '#f5f5f5',
561
551
  dark: '#222222',
562
552
  },
@@ -575,10 +565,11 @@ import $theme from './$theme.mjs'
575
565
 
576
566
  export default {
577
567
  button: {
578
- backgroundColor: $theme.colors.primary,
568
+ backgroundColor: $theme.colors.primary, // automatically replaced with var(--colors-primary) by compiler
579
569
  color: $theme.colors.background,
580
570
  padding: '10px 20px',
581
571
  borderRadius: '4px',
572
+ border: `1px solid ${$theme.colors.surface.var}`, // in case of concatenation, use .var property, otherwise it will be [object Object], sorry
582
573
  },
583
574
  }
584
575
  ```
@@ -591,6 +582,7 @@ button {
591
582
  color: var(--colors-background);
592
583
  padding: 10px 20px;
593
584
  border-radius: 4px;
585
+ border: 1px solid var(--colors-surface);
594
586
  }
595
587
  ```
596
588
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esm-styles",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "A library for working with ESM styles",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",