@styleframe/transpiler 1.0.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.
Files changed (57) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/CHANGELOG.md +12 -0
  3. package/package.json +43 -0
  4. package/src/constants.ts +4 -0
  5. package/src/consume/at-rule.test.ts +339 -0
  6. package/src/consume/at-rule.ts +34 -0
  7. package/src/consume/consume.test.ts +259 -0
  8. package/src/consume/consume.ts +60 -0
  9. package/src/consume/container.test.ts +501 -0
  10. package/src/consume/container.ts +73 -0
  11. package/src/consume/css.test.ts +184 -0
  12. package/src/consume/css.ts +17 -0
  13. package/src/consume/declarations.test.ts +210 -0
  14. package/src/consume/declarations.ts +17 -0
  15. package/src/consume/index.ts +12 -0
  16. package/src/consume/primitive.test.ts +52 -0
  17. package/src/consume/primitive.ts +16 -0
  18. package/src/consume/ref.test.ts +84 -0
  19. package/src/consume/ref.ts +22 -0
  20. package/src/consume/root.test.ts +353 -0
  21. package/src/consume/root.ts +19 -0
  22. package/src/consume/selector.test.ts +441 -0
  23. package/src/consume/selector.ts +17 -0
  24. package/src/consume/theme.test.ts +215 -0
  25. package/src/consume/theme.ts +15 -0
  26. package/src/consume/utility.test.ts +696 -0
  27. package/src/consume/utility.ts +31 -0
  28. package/src/consume/variable.test.ts +197 -0
  29. package/src/consume/variable.ts +20 -0
  30. package/src/defaults.ts +21 -0
  31. package/src/generator/genAtRuleQuery.test.ts +148 -0
  32. package/src/generator/genAtRuleQuery.ts +3 -0
  33. package/src/generator/genDeclaration.test.ts +283 -0
  34. package/src/generator/genDeclaration.ts +9 -0
  35. package/src/generator/genDeclarationsBlock.test.ts +278 -0
  36. package/src/generator/genDeclarationsBlock.ts +7 -0
  37. package/src/generator/genDeclareVariable.test.ts +323 -0
  38. package/src/generator/genDeclareVariable.ts +6 -0
  39. package/src/generator/genInlineAtRule.test.ts +351 -0
  40. package/src/generator/genInlineAtRule.ts +5 -0
  41. package/src/generator/genReferenceVariable.test.ts +392 -0
  42. package/src/generator/genReferenceVariable.ts +5 -0
  43. package/src/generator/genSafePropertyName.test.ts +489 -0
  44. package/src/generator/genSafePropertyName.ts +5 -0
  45. package/src/generator/genSafeVariableName.test.ts +358 -0
  46. package/src/generator/genSafeVariableName.ts +21 -0
  47. package/src/generator/genSelector.test.ts +357 -0
  48. package/src/generator/genSelector.ts +5 -0
  49. package/src/generator/index.ts +9 -0
  50. package/src/index.ts +6 -0
  51. package/src/transpile.test.ts +825 -0
  52. package/src/transpile.ts +21 -0
  53. package/src/types.ts +15 -0
  54. package/src/utils.ts +18 -0
  55. package/src/vite-env.d.ts +1 -0
  56. package/tsconfig.json +7 -0
  57. package/vite.config.ts +5 -0
@@ -0,0 +1,696 @@
1
+ import type {
2
+ Container,
3
+ Root,
4
+ StyleframeOptions,
5
+ Utility,
6
+ } from "@styleframe/core";
7
+ import {
8
+ createModifierFunction,
9
+ createRoot,
10
+ createUtilityFunction,
11
+ isUtility,
12
+ } from "@styleframe/core";
13
+ import { consume } from "./consume";
14
+ import { createUtilityConsumer } from "./utility";
15
+
16
+ describe("createUtilityConsumer", () => {
17
+ let root: Root;
18
+ let parent: Container;
19
+ let utility: ReturnType<typeof createUtilityFunction>;
20
+ let modifier: ReturnType<typeof createModifierFunction>;
21
+
22
+ const consumeUtility = createUtilityConsumer(consume);
23
+ const options: StyleframeOptions = {};
24
+
25
+ beforeEach(() => {
26
+ root = createRoot();
27
+ parent = root;
28
+ utility = createUtilityFunction(parent, root);
29
+ modifier = createModifierFunction(parent, root);
30
+ });
31
+
32
+ it("should generate base utility selector", () => {
33
+ // Create a basic utility
34
+ const createMarginUtility = utility("margin", ({ value }) => ({
35
+ margin: value,
36
+ }));
37
+
38
+ createMarginUtility({
39
+ sm: "8px",
40
+ });
41
+
42
+ const marginUtility = root.children.find(
43
+ (u): u is Utility =>
44
+ u.type === "utility" && u.name === "margin" && u.value === "sm",
45
+ );
46
+ if (!marginUtility) {
47
+ throw new Error("Margin utility not found");
48
+ }
49
+
50
+ const result = consumeUtility(marginUtility, options);
51
+
52
+ const expected = `._margin\\:sm {
53
+ margin: 8px;
54
+ }`;
55
+
56
+ expect(result).toBe(expected);
57
+ });
58
+
59
+ it("should generate multiple utility instances for different values", () => {
60
+ // Create a utility with multiple values
61
+ const createMarginUtility = utility("margin", ({ value }) => ({
62
+ margin: value,
63
+ }));
64
+
65
+ createMarginUtility({
66
+ sm: "8px",
67
+ md: "16px",
68
+ });
69
+
70
+ // Get the sm utility
71
+ const smUtility = root.children.find(
72
+ (u): u is Utility =>
73
+ u.type === "utility" && u.name === "margin" && u.value === "sm",
74
+ );
75
+ if (!smUtility) {
76
+ throw new Error("sm margin utility not found");
77
+ }
78
+
79
+ const smResult = consumeUtility(smUtility, options);
80
+ const expectedSm = `._margin\\:sm {
81
+ margin: 8px;
82
+ }`;
83
+ expect(smResult).toBe(expectedSm);
84
+
85
+ // Get the md utility
86
+ const mdUtility = root.children.find(
87
+ (u): u is Utility =>
88
+ u.type === "utility" && u.name === "margin" && u.value === "md",
89
+ );
90
+ if (!mdUtility) {
91
+ throw new Error("md margin utility not found");
92
+ }
93
+
94
+ const mdResult = consumeUtility(mdUtility, options);
95
+ const expectedMd = `._margin\\:md {
96
+ margin: 16px;
97
+ }`;
98
+ expect(mdResult).toBe(expectedMd);
99
+ });
100
+
101
+ it("should handle boolean values correctly", () => {
102
+ // Create a utility with boolean value
103
+ const createHiddenUtility = utility("hidden", ({ value }) => ({
104
+ display: value === true ? "none" : "block",
105
+ }));
106
+
107
+ createHiddenUtility({
108
+ default: true,
109
+ });
110
+
111
+ const hiddenUtility = root.children.find(
112
+ (u): u is Utility =>
113
+ u.type === "utility" && u.name === "hidden" && u.value === "default",
114
+ );
115
+ if (!hiddenUtility) {
116
+ throw new Error("Hidden utility not found");
117
+ }
118
+
119
+ const result = consumeUtility(hiddenUtility, options);
120
+
121
+ const expected = `._hidden\\:default {
122
+ display: none;
123
+ }`;
124
+
125
+ expect(result).toBe(expected);
126
+ });
127
+
128
+ it("should handle undefined values correctly", () => {
129
+ // Create a utility with undefined value
130
+ const createVisibilityUtility = utility("visible", ({ value }) => ({
131
+ visibility: value || "visible",
132
+ }));
133
+
134
+ createVisibilityUtility({
135
+ default: undefined,
136
+ });
137
+
138
+ const visibilityUtility = root.children.find(
139
+ (u): u is Utility =>
140
+ u.type === "utility" && u.name === "visible" && u.value === "default",
141
+ );
142
+ if (!visibilityUtility) {
143
+ throw new Error("Visible utility not found");
144
+ }
145
+
146
+ const result = consumeUtility(visibilityUtility, options);
147
+
148
+ const expected = `._visible\\:default {
149
+ visibility: visible;
150
+ }`;
151
+
152
+ expect(result).toBe(expected);
153
+ });
154
+
155
+ it("should generate modified utility selectors with a single modifier", () => {
156
+ // Create a hover modifier
157
+ const hoverModifier = modifier("hover", ({ declarations }) => {
158
+ return {
159
+ "&:hover": declarations,
160
+ };
161
+ });
162
+
163
+ // Create a utility with the modifier
164
+ const createMarginUtility = utility("margin", ({ value }) => ({
165
+ margin: value,
166
+ }));
167
+
168
+ createMarginUtility(
169
+ {
170
+ sm: "8px",
171
+ },
172
+ [hoverModifier],
173
+ );
174
+
175
+ // Find the modified utility instance
176
+ const hoverMarginUtility = root.children.find(
177
+ (u): u is Utility =>
178
+ isUtility(u) &&
179
+ u.name === "margin" &&
180
+ u.value === "sm" &&
181
+ u.modifiers.includes("hover"),
182
+ );
183
+ if (!hoverMarginUtility) {
184
+ throw new Error("Hover margin utility not found");
185
+ }
186
+
187
+ const result = consumeUtility(hoverMarginUtility, options);
188
+
189
+ const expected = `._hover\\:margin\\:sm {
190
+ margin: 8px;
191
+ }`;
192
+
193
+ expect(result).toBe(expected);
194
+ });
195
+
196
+ it("should generate modified utility selectors with multiple modifiers", () => {
197
+ // Create modifiers
198
+ const hoverModifier = modifier("hover", ({ declarations }) => {
199
+ return {
200
+ "&:hover": declarations,
201
+ };
202
+ });
203
+
204
+ const focusModifier = modifier("focus", ({ declarations }) => {
205
+ return {
206
+ "&:focus": declarations,
207
+ };
208
+ });
209
+
210
+ // Create a utility with multiple modifiers
211
+ const createMarginUtility = utility("margin", ({ value }) => ({
212
+ margin: value,
213
+ }));
214
+
215
+ createMarginUtility(
216
+ {
217
+ sm: "8px",
218
+ },
219
+ [hoverModifier, focusModifier],
220
+ );
221
+
222
+ // Find the utility with both modifiers
223
+ const hoverFocusMarginUtility = root.children.find(
224
+ (u): u is Utility =>
225
+ isUtility(u) &&
226
+ u.name === "margin" &&
227
+ u.value === "sm" &&
228
+ u.modifiers.includes("hover") &&
229
+ u.modifiers.includes("focus"),
230
+ );
231
+ if (!hoverFocusMarginUtility) {
232
+ throw new Error("Hover+Focus margin utility not found");
233
+ }
234
+
235
+ const result = consumeUtility(hoverFocusMarginUtility, options);
236
+
237
+ const expected = `._focus\\:hover\\:margin\\:sm {
238
+ margin: 8px;
239
+ }`;
240
+
241
+ expect(result).toBe(expected);
242
+ });
243
+
244
+ it("should handle utility with no modifiers", () => {
245
+ // Create a basic utility
246
+ const createMarginUtility = utility("margin", ({ value }) => ({
247
+ margin: value,
248
+ }));
249
+
250
+ createMarginUtility({
251
+ sm: "8px",
252
+ });
253
+
254
+ const marginUtility = root.children.find(
255
+ (u): u is Utility =>
256
+ isUtility(u) &&
257
+ u.name === "margin" &&
258
+ u.value === "sm" &&
259
+ u.modifiers.length === 0,
260
+ );
261
+ if (!marginUtility) {
262
+ throw new Error("Margin utility not found");
263
+ }
264
+
265
+ const result = consumeUtility(marginUtility, options);
266
+
267
+ const expected = `._margin\\:sm {
268
+ margin: 8px;
269
+ }`;
270
+
271
+ expect(result).toBe(expected);
272
+ });
273
+
274
+ it("should generate proper CSS output for complex declarations", () => {
275
+ // Create a utility with complex declarations
276
+ const createFlexUtility = utility("flex", ({ value }) => ({
277
+ display: "flex",
278
+ flexDirection: value === "col" ? "column" : "row",
279
+ gap: "1rem",
280
+ }));
281
+
282
+ createFlexUtility({
283
+ row: "row",
284
+ col: "col",
285
+ });
286
+
287
+ const flexRowUtility = root.children.find(
288
+ (u): u is Utility =>
289
+ u.type === "utility" && u.name === "flex" && u.value === "row",
290
+ );
291
+ if (!flexRowUtility) {
292
+ throw new Error("Flex row utility not found");
293
+ }
294
+
295
+ const rowResult = consumeUtility(flexRowUtility, options);
296
+ const expectedRow = `._flex\\:row {
297
+ \tdisplay: flex;
298
+ \tflex-direction: row;
299
+ \tgap: 1rem;
300
+ }`;
301
+ expect(rowResult).toBe(expectedRow);
302
+
303
+ const flexColUtility = root.children.find(
304
+ (u): u is Utility =>
305
+ u.type === "utility" && u.name === "flex" && u.value === "col",
306
+ );
307
+ if (!flexColUtility) {
308
+ throw new Error("Flex col utility not found");
309
+ }
310
+
311
+ const colResult = consumeUtility(flexColUtility, options);
312
+ const expectedCol = `._flex\\:col {
313
+ \tdisplay: flex;
314
+ \tflex-direction: column;
315
+ \tgap: 1rem;
316
+ }`;
317
+ expect(colResult).toBe(expectedCol);
318
+ });
319
+
320
+ it("should handle empty string as utility value", () => {
321
+ // Create a utility where empty string is a valid value
322
+ const createHiddenUtility = utility("hidden", ({ value }) => ({
323
+ display: value === "" ? "none" : "block",
324
+ }));
325
+
326
+ createHiddenUtility({
327
+ "": "",
328
+ show: "show",
329
+ });
330
+
331
+ const hiddenUtility = root.children.find(
332
+ (u): u is Utility =>
333
+ u.type === "utility" && u.name === "hidden" && u.value === "",
334
+ );
335
+ if (!hiddenUtility) {
336
+ throw new Error("Hidden utility not found");
337
+ }
338
+
339
+ const result = consumeUtility(hiddenUtility, options);
340
+
341
+ const expected = `._hidden {
342
+ \tdisplay: none;
343
+ }`;
344
+
345
+ expect(result).toBe(expected);
346
+ });
347
+
348
+ it("should create utility selectors with proper escaping", () => {
349
+ // Create a utility with special characters that need escaping
350
+ const createSpacingUtility = utility("p", ({ value }) => ({
351
+ padding: value,
352
+ }));
353
+
354
+ createSpacingUtility({
355
+ "1/2": "0.125rem",
356
+ "2.5": "0.625rem",
357
+ });
358
+
359
+ const halfUtility = root.children.find(
360
+ (u): u is Utility =>
361
+ u.type === "utility" && u.name === "p" && u.value === "1/2",
362
+ );
363
+ if (!halfUtility) {
364
+ throw new Error("P 1/2 utility not found");
365
+ }
366
+
367
+ const halfResult = consumeUtility(halfUtility, options);
368
+ const expectedHalf = `._p\\:1/2 {
369
+ \tpadding: 0.125rem;
370
+ }`;
371
+ expect(halfResult).toBe(expectedHalf);
372
+
373
+ const decimalUtility = root.children.find(
374
+ (u): u is Utility =>
375
+ u.type === "utility" && u.name === "p" && u.value === "2.5",
376
+ );
377
+ if (!decimalUtility) {
378
+ throw new Error("P 2.5 utility not found");
379
+ }
380
+
381
+ const decimalResult = consumeUtility(decimalUtility, options);
382
+ const expectedDecimal = `._p\\:2.5 {
383
+ \tpadding: 0.625rem;
384
+ }`;
385
+ expect(decimalResult).toBe(expectedDecimal);
386
+ });
387
+
388
+ it("should handle modifiers with complex transformations", () => {
389
+ // Create a complex modifier
390
+ const groupHoverModifier = modifier("group-hover", ({ declarations }) => {
391
+ return {
392
+ ".group:hover &": declarations,
393
+ };
394
+ });
395
+
396
+ // Create a utility with the modifier
397
+ const createMarginUtility = utility("margin", ({ value }) => ({
398
+ margin: value,
399
+ }));
400
+
401
+ createMarginUtility(
402
+ {
403
+ sm: "8px",
404
+ },
405
+ [groupHoverModifier],
406
+ );
407
+
408
+ const groupHoverMarginUtility = root.children.find(
409
+ (u): u is Utility =>
410
+ isUtility(u) &&
411
+ u.name === "margin" &&
412
+ u.value === "sm" &&
413
+ u.modifiers.includes("group-hover"),
414
+ );
415
+ if (!groupHoverMarginUtility) {
416
+ throw new Error("Group hover margin utility not found");
417
+ }
418
+
419
+ const result = consumeUtility(groupHoverMarginUtility, options);
420
+
421
+ const expected = `._group-hover\\:margin\\:sm {
422
+ \tmargin: 8px;
423
+ }`;
424
+
425
+ expect(result).toBe(expected);
426
+ });
427
+
428
+ it("should handle multi-key modifiers correctly", () => {
429
+ // Create a modifier with multiple keys
430
+ const breakpointModifier = modifier(
431
+ ["sm", "md", "lg"],
432
+ ({ declarations }) => {
433
+ // Note: In the actual implementation, the specific key would be used
434
+ // This is a simplified version for testing
435
+ return {
436
+ "@media (min-width: 640px)": declarations,
437
+ };
438
+ },
439
+ );
440
+
441
+ // Create a utility with the multi-key modifier
442
+ const createMarginUtility = utility("margin", ({ value }) => ({
443
+ margin: value,
444
+ }));
445
+
446
+ createMarginUtility(
447
+ {
448
+ base: "8px",
449
+ },
450
+ [breakpointModifier],
451
+ );
452
+
453
+ // Find utilities for each breakpoint key
454
+ const smMarginUtility = root.children.find(
455
+ (u): u is Utility =>
456
+ isUtility(u) &&
457
+ u.name === "margin" &&
458
+ u.value === "base" &&
459
+ u.modifiers.includes("sm"),
460
+ );
461
+ if (smMarginUtility) {
462
+ const result = consumeUtility(smMarginUtility, options);
463
+ const expected = `._sm\\:margin\\:base {
464
+ \tmargin: 8px;
465
+ }`;
466
+ expect(result).toBe(expected);
467
+ }
468
+ });
469
+
470
+ it("should combine multiple different modifiers correctly", () => {
471
+ // Create different modifiers
472
+ const hoverModifier = modifier("hover", ({ declarations }) => {
473
+ return {
474
+ "&:hover": declarations,
475
+ };
476
+ });
477
+
478
+ const responsiveModifier = modifier("responsive", ({ declarations }) => {
479
+ return {
480
+ "@media (min-width: 768px)": declarations,
481
+ };
482
+ });
483
+
484
+ // Create a utility with multiple modifiers
485
+ const createMarginUtility = utility("margin", ({ value }) => ({
486
+ margin: value,
487
+ }));
488
+
489
+ createMarginUtility(
490
+ {
491
+ sm: "8px",
492
+ },
493
+ [hoverModifier, responsiveModifier],
494
+ );
495
+
496
+ // Find the utility with both modifiers
497
+ const combinedUtility = root.children.find(
498
+ (u): u is Utility =>
499
+ isUtility(u) &&
500
+ u.name === "margin" &&
501
+ u.value === "sm" &&
502
+ u.modifiers.includes("hover") &&
503
+ u.modifiers.includes("responsive"),
504
+ );
505
+ if (!combinedUtility) {
506
+ throw new Error("Combined modifier utility not found");
507
+ }
508
+
509
+ const result = consumeUtility(combinedUtility, options);
510
+
511
+ const expected = `._hover\\:responsive\\:margin\\:sm {
512
+ \tmargin: 8px;
513
+ }`;
514
+
515
+ expect(result).toBe(expected);
516
+ });
517
+
518
+ it("should handle nested declarations properly", () => {
519
+ // Create a utility with nested declarations
520
+ const createButtonUtility = utility("btn", ({ value }) => ({
521
+ display: "inline-block",
522
+ padding: value === "sm" ? "0.5rem 1rem" : "1rem 2rem",
523
+ "&:hover": {
524
+ opacity: "0.8",
525
+ },
526
+ "&:focus": {
527
+ outline: "2px solid blue",
528
+ },
529
+ }));
530
+
531
+ createButtonUtility({
532
+ sm: "sm",
533
+ lg: "lg",
534
+ });
535
+
536
+ const smButtonUtility = root.children.find(
537
+ (u): u is Utility =>
538
+ u.type === "utility" && u.name === "btn" && u.value === "sm",
539
+ );
540
+ if (!smButtonUtility) {
541
+ throw new Error("Small button utility not found");
542
+ }
543
+
544
+ const result = consumeUtility(smButtonUtility, options);
545
+
546
+ const expected = `._btn\\:sm {
547
+ \tdisplay: inline-block;
548
+ \tpadding: 0.5rem 1rem;
549
+ \t
550
+ \t&:hover {
551
+ \t\topacity: 0.8;
552
+ \t}
553
+ \t
554
+ \t&:focus {
555
+ \t\toutline: 2px solid blue;
556
+ \t}
557
+ }`;
558
+
559
+ expect(result).toBe(expected);
560
+ });
561
+
562
+ it("should handle utility with CSS variables", () => {
563
+ // Create a utility that uses CSS variables
564
+ const createColorUtility = utility("text", ({ value }) => ({
565
+ color: `var(--color-${value})`,
566
+ "--text-opacity": "1",
567
+ }));
568
+
569
+ createColorUtility({
570
+ primary: "primary",
571
+ secondary: "secondary",
572
+ });
573
+
574
+ const primaryTextUtility = root.children.find(
575
+ (u): u is Utility =>
576
+ u.type === "utility" && u.name === "text" && u.value === "primary",
577
+ );
578
+ if (!primaryTextUtility) {
579
+ throw new Error("Primary text utility not found");
580
+ }
581
+
582
+ const result = consumeUtility(primaryTextUtility, options);
583
+
584
+ const expected = `._text\\:primary {
585
+ \tcolor: var(--color-primary);
586
+ \t--text-opacity: 1;
587
+ }`;
588
+
589
+ expect(result).toBe(expected);
590
+ });
591
+
592
+ it("should handle utility with custom selector function", () => {
593
+ const customOptions: StyleframeOptions = {
594
+ utilities: {
595
+ selector: ({ name, value, modifiers }) => {
596
+ const parts = [...modifiers, name];
597
+ if (value) parts.push(value);
598
+ return `.${parts.join("-")}`;
599
+ },
600
+ },
601
+ };
602
+
603
+ // Create a basic utility
604
+ const createMarginUtility = utility("margin", ({ value }) => ({
605
+ margin: value,
606
+ }));
607
+
608
+ createMarginUtility({
609
+ sm: "8px",
610
+ });
611
+
612
+ const marginUtility = root.children.find(
613
+ (u): u is Utility =>
614
+ u.type === "utility" && u.name === "margin" && u.value === "sm",
615
+ );
616
+ if (!marginUtility) {
617
+ throw new Error("Margin utility not found");
618
+ }
619
+
620
+ const result = consumeUtility(marginUtility, customOptions);
621
+
622
+ const expected = `.margin-sm {
623
+ margin: 8px;
624
+ }`;
625
+
626
+ expect(result).toBe(expected);
627
+ });
628
+
629
+ it("should generate all modifier combinations", () => {
630
+ // Create two modifiers
631
+ const hoverModifier = modifier("hover", ({ declarations }) => {
632
+ return {
633
+ "&:hover": declarations,
634
+ };
635
+ });
636
+
637
+ const focusModifier = modifier("focus", ({ declarations }) => {
638
+ return {
639
+ "&:focus": declarations,
640
+ };
641
+ });
642
+
643
+ // Create a utility with both modifiers
644
+ const createMarginUtility = utility("margin", ({ value }) => ({
645
+ margin: value,
646
+ }));
647
+
648
+ createMarginUtility(
649
+ {
650
+ sm: "8px",
651
+ },
652
+ [hoverModifier, focusModifier],
653
+ );
654
+
655
+ // Check that all combinations are created
656
+ const baseUtility = root.children.find(
657
+ (u): u is Utility =>
658
+ isUtility(u) &&
659
+ u.name === "margin" &&
660
+ u.value === "sm" &&
661
+ u.modifiers.length === 0,
662
+ );
663
+ expect(baseUtility).toBeDefined();
664
+
665
+ const hoverOnlyUtility = root.children.find(
666
+ (u): u is Utility =>
667
+ isUtility(u) &&
668
+ u.name === "margin" &&
669
+ u.value === "sm" &&
670
+ u.modifiers.length === 1 &&
671
+ u.modifiers.includes("hover"),
672
+ );
673
+ expect(hoverOnlyUtility).toBeDefined();
674
+
675
+ const focusOnlyUtility = root.children.find(
676
+ (u): u is Utility =>
677
+ isUtility(u) &&
678
+ u.name === "margin" &&
679
+ u.value === "sm" &&
680
+ u.modifiers.length === 1 &&
681
+ u.modifiers.includes("focus"),
682
+ );
683
+ expect(focusOnlyUtility).toBeDefined();
684
+
685
+ const bothModifiersUtility = root.children.find(
686
+ (u): u is Utility =>
687
+ isUtility(u) &&
688
+ u.name === "margin" &&
689
+ u.value === "sm" &&
690
+ u.modifiers.length === 2 &&
691
+ u.modifiers.includes("hover") &&
692
+ u.modifiers.includes("focus"),
693
+ );
694
+ expect(bothModifiersUtility).toBeDefined();
695
+ });
696
+ });
@@ -0,0 +1,31 @@
1
+ import type { StyleframeOptions, Utility } from "@styleframe/core";
2
+ import { defaultUtilitySelectorFn } from "../defaults";
3
+ import type { ConsumeFunction } from "../types";
4
+ import { createContainerConsumer } from "./container";
5
+
6
+ /**
7
+ * Consumes a utility instance, equivalent to setting a utility CSS selector
8
+ */
9
+ export function createUtilityConsumer(consume: ConsumeFunction) {
10
+ const consumeContainer = createContainerConsumer(consume);
11
+
12
+ return function consumeUtility(
13
+ instance: Utility,
14
+ options: StyleframeOptions,
15
+ ): string {
16
+ const result: string[] = [];
17
+
18
+ const utilitySelectorFn =
19
+ options.utilities?.selector ?? defaultUtilitySelectorFn;
20
+ const utilitySelector = utilitySelectorFn({
21
+ name: instance.name,
22
+ value: instance.value,
23
+ modifiers: instance.modifiers,
24
+ });
25
+
26
+ // Create base utility selector
27
+ result.push(consumeContainer(utilitySelector, instance, options));
28
+
29
+ return result.join("\n\n");
30
+ };
31
+ }