@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.
- package/.tsbuildinfo +1 -0
- package/CHANGELOG.md +12 -0
- package/package.json +43 -0
- package/src/constants.ts +4 -0
- package/src/consume/at-rule.test.ts +339 -0
- package/src/consume/at-rule.ts +34 -0
- package/src/consume/consume.test.ts +259 -0
- package/src/consume/consume.ts +60 -0
- package/src/consume/container.test.ts +501 -0
- package/src/consume/container.ts +73 -0
- package/src/consume/css.test.ts +184 -0
- package/src/consume/css.ts +17 -0
- package/src/consume/declarations.test.ts +210 -0
- package/src/consume/declarations.ts +17 -0
- package/src/consume/index.ts +12 -0
- package/src/consume/primitive.test.ts +52 -0
- package/src/consume/primitive.ts +16 -0
- package/src/consume/ref.test.ts +84 -0
- package/src/consume/ref.ts +22 -0
- package/src/consume/root.test.ts +353 -0
- package/src/consume/root.ts +19 -0
- package/src/consume/selector.test.ts +441 -0
- package/src/consume/selector.ts +17 -0
- package/src/consume/theme.test.ts +215 -0
- package/src/consume/theme.ts +15 -0
- package/src/consume/utility.test.ts +696 -0
- package/src/consume/utility.ts +31 -0
- package/src/consume/variable.test.ts +197 -0
- package/src/consume/variable.ts +20 -0
- package/src/defaults.ts +21 -0
- package/src/generator/genAtRuleQuery.test.ts +148 -0
- package/src/generator/genAtRuleQuery.ts +3 -0
- package/src/generator/genDeclaration.test.ts +283 -0
- package/src/generator/genDeclaration.ts +9 -0
- package/src/generator/genDeclarationsBlock.test.ts +278 -0
- package/src/generator/genDeclarationsBlock.ts +7 -0
- package/src/generator/genDeclareVariable.test.ts +323 -0
- package/src/generator/genDeclareVariable.ts +6 -0
- package/src/generator/genInlineAtRule.test.ts +351 -0
- package/src/generator/genInlineAtRule.ts +5 -0
- package/src/generator/genReferenceVariable.test.ts +392 -0
- package/src/generator/genReferenceVariable.ts +5 -0
- package/src/generator/genSafePropertyName.test.ts +489 -0
- package/src/generator/genSafePropertyName.ts +5 -0
- package/src/generator/genSafeVariableName.test.ts +358 -0
- package/src/generator/genSafeVariableName.ts +21 -0
- package/src/generator/genSelector.test.ts +357 -0
- package/src/generator/genSelector.ts +5 -0
- package/src/generator/index.ts +9 -0
- package/src/index.ts +6 -0
- package/src/transpile.test.ts +825 -0
- package/src/transpile.ts +21 -0
- package/src/types.ts +15 -0
- package/src/utils.ts +18 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +7 -0
- 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
|
+
}
|