@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,441 @@
|
|
|
1
|
+
import type { Root, StyleframeOptions } from "@styleframe/core";
|
|
2
|
+
import {
|
|
3
|
+
createCssFunction,
|
|
4
|
+
createRefFunction,
|
|
5
|
+
createRoot,
|
|
6
|
+
createSelectorFunction,
|
|
7
|
+
createVariableFunction,
|
|
8
|
+
isSelector,
|
|
9
|
+
} from "@styleframe/core";
|
|
10
|
+
import { createSelectorConsumer } from "./selector";
|
|
11
|
+
import { consume } from "./consume";
|
|
12
|
+
|
|
13
|
+
describe("createSelectorConsumer", () => {
|
|
14
|
+
let root: Root;
|
|
15
|
+
let variable: ReturnType<typeof createVariableFunction>;
|
|
16
|
+
let ref: ReturnType<typeof createRefFunction>;
|
|
17
|
+
let selector: ReturnType<typeof createSelectorFunction>;
|
|
18
|
+
let css: ReturnType<typeof createCssFunction>;
|
|
19
|
+
|
|
20
|
+
const consumeSelector = createSelectorConsumer(consume);
|
|
21
|
+
const options: StyleframeOptions = {};
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
root = createRoot();
|
|
25
|
+
variable = createVariableFunction(root, root);
|
|
26
|
+
ref = createRefFunction(root, root);
|
|
27
|
+
selector = createSelectorFunction(root, root);
|
|
28
|
+
css = createCssFunction(root, root);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should create a basic selector with declarations", () => {
|
|
32
|
+
const buttonSelector = selector(".button", {
|
|
33
|
+
padding: "0.5rem 1rem",
|
|
34
|
+
backgroundColor: "#006cff",
|
|
35
|
+
color: "white",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = consumeSelector(buttonSelector, options);
|
|
39
|
+
|
|
40
|
+
expect(result).toBe(`.button {
|
|
41
|
+
\tpadding: 0.5rem 1rem;
|
|
42
|
+
\tbackground-color: #006cff;
|
|
43
|
+
\tcolor: white;
|
|
44
|
+
}`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should create a selector with variables", () => {
|
|
48
|
+
const buttonSelector = selector(".button", ({ variable }) => {
|
|
49
|
+
variable("button-color", "#006cff");
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
padding: "0.5rem 1rem",
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = consumeSelector(buttonSelector, options);
|
|
57
|
+
|
|
58
|
+
expect(result).toBe(`.button {
|
|
59
|
+
\t--button-color: #006cff;
|
|
60
|
+
\t
|
|
61
|
+
\tpadding: 0.5rem 1rem;
|
|
62
|
+
}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should create a selector with child selectors", () => {
|
|
66
|
+
const buttonSelector = selector(".button", ({ selector }) => {
|
|
67
|
+
selector("&:hover", {
|
|
68
|
+
backgroundColor: "#0056cc",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
padding: "0.5rem 1rem",
|
|
73
|
+
backgroundColor: "#006cff",
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = consumeSelector(buttonSelector, options);
|
|
78
|
+
|
|
79
|
+
expect(result).toBe(`.button {
|
|
80
|
+
\tpadding: 0.5rem 1rem;
|
|
81
|
+
\tbackground-color: #006cff;
|
|
82
|
+
\t
|
|
83
|
+
\t&:hover {
|
|
84
|
+
\t\tbackground-color: #0056cc;
|
|
85
|
+
\t}
|
|
86
|
+
}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should create a selector with variables, declarations and children", () => {
|
|
90
|
+
const buttonSelector = selector(".button", ({ variable, selector }) => {
|
|
91
|
+
const colorVar = variable("button-color", "#006cff");
|
|
92
|
+
const hoverVar = variable("button-hover-color", "#0056cc");
|
|
93
|
+
|
|
94
|
+
selector("&:hover", {
|
|
95
|
+
backgroundColor: ref(hoverVar),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
padding: "0.5rem 1rem",
|
|
100
|
+
backgroundColor: ref(colorVar),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = consumeSelector(buttonSelector, options);
|
|
105
|
+
|
|
106
|
+
expect(result).toBe(`.button {
|
|
107
|
+
\t--button-color: #006cff;
|
|
108
|
+
\t--button-hover-color: #0056cc;
|
|
109
|
+
\t
|
|
110
|
+
\tpadding: 0.5rem 1rem;
|
|
111
|
+
\tbackground-color: var(--button-color);
|
|
112
|
+
\t
|
|
113
|
+
\t&:hover {
|
|
114
|
+
\t\tbackground-color: var(--button-hover-color);
|
|
115
|
+
\t}
|
|
116
|
+
}`);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle empty declarations", () => {
|
|
120
|
+
const buttonSelector = selector(".button", ({ selector }) => {
|
|
121
|
+
selector("&:hover", {
|
|
122
|
+
backgroundColor: "#0056cc",
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = consumeSelector(buttonSelector, options);
|
|
127
|
+
|
|
128
|
+
expect(result).toBe(`.button {
|
|
129
|
+
\t&:hover {
|
|
130
|
+
\t\tbackground-color: #0056cc;
|
|
131
|
+
\t}
|
|
132
|
+
}`);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should handle empty variables", () => {
|
|
136
|
+
const buttonSelector = selector(".button", {
|
|
137
|
+
padding: "0.5rem 1rem",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const result = consumeSelector(buttonSelector, options);
|
|
141
|
+
|
|
142
|
+
expect(result).toBe(`.button {
|
|
143
|
+
\tpadding: 0.5rem 1rem;
|
|
144
|
+
}`);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should handle empty children", () => {
|
|
148
|
+
const buttonSelector = selector(".button", ({ variable }) => {
|
|
149
|
+
variable("button-color", "#006cff");
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
padding: "0.5rem 1rem",
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = consumeSelector(buttonSelector, options);
|
|
157
|
+
|
|
158
|
+
expect(result).toBe(`.button {
|
|
159
|
+
\t--button-color: #006cff;
|
|
160
|
+
\t
|
|
161
|
+
\tpadding: 0.5rem 1rem;
|
|
162
|
+
}`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should handle completely empty selector", () => {
|
|
166
|
+
const emptySelector = selector(".empty", {});
|
|
167
|
+
|
|
168
|
+
const result = consumeSelector(emptySelector, options);
|
|
169
|
+
|
|
170
|
+
expect(result).toBe(`.empty {}`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should respect custom indentation in options", () => {
|
|
174
|
+
const customOptions: StyleframeOptions = {
|
|
175
|
+
indent: " ", // 4 spaces instead of default 2
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const buttonSelector = selector(".button", {
|
|
179
|
+
padding: "0.5rem 1rem",
|
|
180
|
+
backgroundColor: "#006cff",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = consumeSelector(buttonSelector, customOptions);
|
|
184
|
+
|
|
185
|
+
expect(result).toBe(`.button {
|
|
186
|
+
\tpadding: 0.5rem 1rem;
|
|
187
|
+
\tbackground-color: #006cff;
|
|
188
|
+
}`);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should handle nested children multiple levels deep", () => {
|
|
192
|
+
const buttonSelector = selector(".button", ({ selector }) => {
|
|
193
|
+
selector("&:hover", ({ selector }) => {
|
|
194
|
+
selector("&:active", {
|
|
195
|
+
transform: "scale(0.98)",
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
backgroundColor: "#0056cc",
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
padding: "0.5rem 1rem",
|
|
205
|
+
backgroundColor: "#006cff",
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const result = consumeSelector(buttonSelector, options);
|
|
210
|
+
|
|
211
|
+
expect(result).toBe(`.button {
|
|
212
|
+
\tpadding: 0.5rem 1rem;
|
|
213
|
+
\tbackground-color: #006cff;
|
|
214
|
+
\t
|
|
215
|
+
\t&:hover {
|
|
216
|
+
\t\tbackground-color: #0056cc;
|
|
217
|
+
\t\t
|
|
218
|
+
\t\t&:active {
|
|
219
|
+
\t\t\ttransform: scale(0.98);
|
|
220
|
+
\t\t}
|
|
221
|
+
\t}
|
|
222
|
+
}`);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should handle complex nested selector structures", () => {
|
|
226
|
+
const buttonSelector = selector(".button", ({ selector, variable }) => {
|
|
227
|
+
const colorVar = variable("button-color", "#006cff");
|
|
228
|
+
|
|
229
|
+
selector("&:hover", ({ selector }) => {
|
|
230
|
+
selector("&:active", {
|
|
231
|
+
transform: "scale(0.98)",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
backgroundColor: "#0056cc",
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
selector("& > .icon", {
|
|
240
|
+
marginRight: "0.5rem",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
padding: "0.5rem 1rem",
|
|
245
|
+
backgroundColor: ref(colorVar),
|
|
246
|
+
display: "flex",
|
|
247
|
+
alignItems: "center",
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const result = consumeSelector(buttonSelector, options);
|
|
252
|
+
|
|
253
|
+
expect(result).toBe(`.button {
|
|
254
|
+
\t--button-color: #006cff;
|
|
255
|
+
\t
|
|
256
|
+
\tpadding: 0.5rem 1rem;
|
|
257
|
+
\tbackground-color: var(--button-color);
|
|
258
|
+
\tdisplay: flex;
|
|
259
|
+
\talign-items: center;
|
|
260
|
+
\t
|
|
261
|
+
\t&:hover {
|
|
262
|
+
\t\tbackground-color: #0056cc;
|
|
263
|
+
\t\t
|
|
264
|
+
\t\t&:active {
|
|
265
|
+
\t\t\ttransform: scale(0.98);
|
|
266
|
+
\t\t}
|
|
267
|
+
\t}
|
|
268
|
+
\t
|
|
269
|
+
\t& > .icon {
|
|
270
|
+
\t\tmargin-right: 0.5rem;
|
|
271
|
+
\t}
|
|
272
|
+
}`);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should handle selectors with inline nesting syntax", () => {
|
|
276
|
+
const cardSelector = selector(".card", {
|
|
277
|
+
padding: "1rem",
|
|
278
|
+
borderRadius: "8px",
|
|
279
|
+
|
|
280
|
+
// Inline nested selectors
|
|
281
|
+
".card-title": {
|
|
282
|
+
fontSize: "1.5rem",
|
|
283
|
+
fontWeight: "bold",
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
".card-content": {
|
|
287
|
+
marginTop: "0.5rem",
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Extract the created nested selectors
|
|
292
|
+
const nestedSelectors = cardSelector.children.filter((child) =>
|
|
293
|
+
isSelector(child),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Confirm we have the expected nested selectors
|
|
297
|
+
expect(nestedSelectors.length).toBe(2);
|
|
298
|
+
|
|
299
|
+
const result = consumeSelector(cardSelector, options);
|
|
300
|
+
|
|
301
|
+
expect(result).toBe(`.card {
|
|
302
|
+
\tpadding: 1rem;
|
|
303
|
+
\tborder-radius: 8px;
|
|
304
|
+
\t
|
|
305
|
+
\t.card-title {
|
|
306
|
+
\t\tfont-size: 1.5rem;
|
|
307
|
+
\t\tfont-weight: bold;
|
|
308
|
+
\t}
|
|
309
|
+
\t
|
|
310
|
+
\t.card-content {
|
|
311
|
+
\t\tmargin-top: 0.5rem;
|
|
312
|
+
\t}
|
|
313
|
+
}`);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should handle selectors with pseudo-classes", () => {
|
|
317
|
+
const buttonSelector = selector(".button", {
|
|
318
|
+
padding: "0.5rem 1rem",
|
|
319
|
+
backgroundColor: "#006cff",
|
|
320
|
+
color: "white",
|
|
321
|
+
transition: "background-color 0.2s",
|
|
322
|
+
|
|
323
|
+
"&:hover": {
|
|
324
|
+
backgroundColor: "#0056cc",
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
"&:active": {
|
|
328
|
+
backgroundColor: "#004099",
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
"&::before": {
|
|
332
|
+
content: '""',
|
|
333
|
+
display: "inline-block",
|
|
334
|
+
marginRight: "0.5rem",
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const result = consumeSelector(buttonSelector, options);
|
|
339
|
+
|
|
340
|
+
expect(result).toBe(`.button {
|
|
341
|
+
\tpadding: 0.5rem 1rem;
|
|
342
|
+
\tbackground-color: #006cff;
|
|
343
|
+
\tcolor: white;
|
|
344
|
+
\ttransition: background-color 0.2s;
|
|
345
|
+
\t
|
|
346
|
+
\t&:hover {
|
|
347
|
+
\t\tbackground-color: #0056cc;
|
|
348
|
+
\t}
|
|
349
|
+
\t
|
|
350
|
+
\t&:active {
|
|
351
|
+
\t\tbackground-color: #004099;
|
|
352
|
+
\t}
|
|
353
|
+
\t
|
|
354
|
+
\t&::before {
|
|
355
|
+
\t\tcontent: "";
|
|
356
|
+
\t\tdisplay: inline-block;
|
|
357
|
+
\t\tmargin-right: 0.5rem;
|
|
358
|
+
\t}
|
|
359
|
+
}`);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should handle selectors with variable references for consistent theming", () => {
|
|
363
|
+
const buttonSelector = selector(".button", ({ variable }) => {
|
|
364
|
+
const borderRadiusSm = variable("border-radius-sm", "4px");
|
|
365
|
+
const colorPrimary = variable("color-primary", "#006cff");
|
|
366
|
+
const colorPrimaryDark = variable("color-primary-dark", "#0056cc");
|
|
367
|
+
const spacingMd = variable("spacing-md", "1rem");
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
backgroundColor: ref(colorPrimary),
|
|
371
|
+
borderRadius: ref(borderRadiusSm),
|
|
372
|
+
color: "white",
|
|
373
|
+
padding: ref(spacingMd),
|
|
374
|
+
|
|
375
|
+
"&:hover": {
|
|
376
|
+
backgroundColor: ref(colorPrimaryDark),
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const result = consumeSelector(buttonSelector, options);
|
|
382
|
+
|
|
383
|
+
expect(result).toBe(`.button {
|
|
384
|
+
\t--border-radius-sm: 4px;
|
|
385
|
+
\t--color-primary: #006cff;
|
|
386
|
+
\t--color-primary-dark: #0056cc;
|
|
387
|
+
\t--spacing-md: 1rem;
|
|
388
|
+
\t
|
|
389
|
+
\tbackground-color: var(--color-primary);
|
|
390
|
+
\tborder-radius: var(--border-radius-sm);
|
|
391
|
+
\tcolor: white;
|
|
392
|
+
\tpadding: var(--spacing-md);
|
|
393
|
+
\t
|
|
394
|
+
\t&:hover {
|
|
395
|
+
\t\tbackground-color: var(--color-primary-dark);
|
|
396
|
+
\t}
|
|
397
|
+
}`);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should handle selectors with media query nesting", () => {
|
|
401
|
+
const cardSelector = selector(".card", {
|
|
402
|
+
width: "100%",
|
|
403
|
+
padding: "1rem",
|
|
404
|
+
|
|
405
|
+
"@media (min-width: 768px)": {
|
|
406
|
+
width: "50%",
|
|
407
|
+
padding: "2rem",
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const result = consumeSelector(cardSelector, options);
|
|
412
|
+
|
|
413
|
+
expect(result).toBe(`.card {
|
|
414
|
+
\twidth: 100%;
|
|
415
|
+
\tpadding: 1rem;
|
|
416
|
+
\t
|
|
417
|
+
\t@media (min-width: 768px) {
|
|
418
|
+
\t\twidth: 50%;
|
|
419
|
+
\t\tpadding: 2rem;
|
|
420
|
+
\t}
|
|
421
|
+
}`);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should handle selectors with calc() expressions", () => {
|
|
425
|
+
const spacingBase = variable("spacing-base", "8px");
|
|
426
|
+
|
|
427
|
+
const containerSelector = selector(".container", {
|
|
428
|
+
padding: "calc(2 * 1rem)",
|
|
429
|
+
margin: css`calc(${ref(spacingBase)} * 2)`,
|
|
430
|
+
width: "calc(100% - 2rem)",
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const result = consumeSelector(containerSelector, options);
|
|
434
|
+
|
|
435
|
+
expect(result).toBe(`.container {
|
|
436
|
+
\tpadding: calc(2 * 1rem);
|
|
437
|
+
\tmargin: calc(var(--spacing-base) * 2);
|
|
438
|
+
\twidth: calc(100% - 2rem);
|
|
439
|
+
}`);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Selector, StyleframeOptions } from "@styleframe/core";
|
|
2
|
+
import type { ConsumeFunction } from "../types";
|
|
3
|
+
import { createContainerConsumer } from "./container";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Consumes a selector instance, equivalent to setting a CSS selector
|
|
7
|
+
*/
|
|
8
|
+
export function createSelectorConsumer(consume: ConsumeFunction) {
|
|
9
|
+
const consumeContainer = createContainerConsumer(consume);
|
|
10
|
+
|
|
11
|
+
return function consumeSelector(
|
|
12
|
+
instance: Selector,
|
|
13
|
+
options: StyleframeOptions,
|
|
14
|
+
): string {
|
|
15
|
+
return consumeContainer(instance.query, instance, options);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { Root, StyleframeOptions } from "@styleframe/core";
|
|
2
|
+
import {
|
|
3
|
+
createCssFunction,
|
|
4
|
+
createRefFunction,
|
|
5
|
+
createRoot,
|
|
6
|
+
createThemeFunction,
|
|
7
|
+
createVariableFunction,
|
|
8
|
+
} from "@styleframe/core";
|
|
9
|
+
import { consume } from "./consume";
|
|
10
|
+
import { createThemeConsumer } from "./theme";
|
|
11
|
+
|
|
12
|
+
describe("createThemeConsumer", () => {
|
|
13
|
+
let root: Root;
|
|
14
|
+
let theme: ReturnType<typeof createThemeFunction>;
|
|
15
|
+
let variable: ReturnType<typeof createVariableFunction>;
|
|
16
|
+
let ref: ReturnType<typeof createRefFunction>;
|
|
17
|
+
let css: ReturnType<typeof createCssFunction>;
|
|
18
|
+
|
|
19
|
+
const consumeTheme = createThemeConsumer(consume);
|
|
20
|
+
const options: StyleframeOptions = {};
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
root = createRoot();
|
|
24
|
+
theme = createThemeFunction(root, root);
|
|
25
|
+
variable = createVariableFunction(root, root);
|
|
26
|
+
ref = createRefFunction(root, root);
|
|
27
|
+
css = createCssFunction(root, root);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should convert a basic theme to CSS with default selector format", () => {
|
|
31
|
+
const lightTheme = theme("light", ({ variable: v }) => {
|
|
32
|
+
v("color-primary", "#0066ff");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(consumeTheme(lightTheme, options)).toBe(
|
|
36
|
+
'[data-theme="light"] {\n\t--color-primary: #0066ff;\n}',
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should handle themes with multiple variables", () => {
|
|
41
|
+
const darkTheme = theme("dark", ({ variable: v }) => {
|
|
42
|
+
v("color-primary", "#4d9eff");
|
|
43
|
+
v("color-secondary", "#ff6b6b");
|
|
44
|
+
v("background", "#1a1a1a");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = consumeTheme(darkTheme, options);
|
|
48
|
+
expect(result).toEqual(
|
|
49
|
+
'[data-theme="dark"] {\n\t--color-primary: #4d9eff;\n\t--color-secondary: #ff6b6b;\n\t--background: #1a1a1a;\n}',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should handle themes with nested selectors", () => {
|
|
54
|
+
const themeWithSelectors = theme(
|
|
55
|
+
"colorful",
|
|
56
|
+
({ variable: v, selector: s }) => {
|
|
57
|
+
v("primary-color", "#ff0066");
|
|
58
|
+
s(".button", {
|
|
59
|
+
color: ref("primary-color"),
|
|
60
|
+
backgroundColor: "white",
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const result = consumeTheme(themeWithSelectors, options);
|
|
66
|
+
expect(result).toEqual(`[data-theme="colorful"] {
|
|
67
|
+
\t--primary-color: #ff0066;
|
|
68
|
+
\t
|
|
69
|
+
\t.button {
|
|
70
|
+
\t\tcolor: var(--primary-color);
|
|
71
|
+
\t\tbackground-color: white;
|
|
72
|
+
\t}
|
|
73
|
+
}`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should respect custom theme selector format", () => {
|
|
77
|
+
const customOptions: StyleframeOptions = {
|
|
78
|
+
theme: {
|
|
79
|
+
selector: ({ name }) => `.theme-${name}`,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const customTheme = theme("neon", ({ variable: v }) => {
|
|
84
|
+
v("accent-color", "#00ff88");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(consumeTheme(customTheme, customOptions)).toBe(
|
|
88
|
+
".theme-neon {\n\t--accent-color: #00ff88;\n}",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should handle themes with variable prefix", () => {
|
|
93
|
+
const prefixOptions: StyleframeOptions = {
|
|
94
|
+
variables: {
|
|
95
|
+
name: ({ name }) => `--app-${name}`,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const prefixedTheme = theme("branded", ({ variable: v }) => {
|
|
100
|
+
v("brand-color", "#123456");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(consumeTheme(prefixedTheme, prefixOptions)).toBe(
|
|
104
|
+
'[data-theme="branded"] {\n\t--app-brand-color: #123456;\n}',
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should handle empty themes", () => {
|
|
109
|
+
const emptyTheme = theme("empty", () => {});
|
|
110
|
+
|
|
111
|
+
expect(consumeTheme(emptyTheme, options)).toBe('[data-theme="empty"] {}');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should handle themes with only nested children", () => {
|
|
115
|
+
const childrenOnlyTheme = theme("structure", ({ selector: s }) => {
|
|
116
|
+
s(".header", {
|
|
117
|
+
padding: "1rem",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
s(".footer", {
|
|
121
|
+
margin: "2rem 0",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = consumeTheme(childrenOnlyTheme, options);
|
|
126
|
+
expect(result).toEqual(
|
|
127
|
+
`[data-theme="structure"] {
|
|
128
|
+
\t.header {
|
|
129
|
+
\t\tpadding: 1rem;
|
|
130
|
+
\t}
|
|
131
|
+
\t
|
|
132
|
+
\t.footer {
|
|
133
|
+
\t\tmargin: 2rem 0;
|
|
134
|
+
\t}
|
|
135
|
+
}`,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should handle themes with complex nested structures", () => {
|
|
140
|
+
const complexTheme = theme(
|
|
141
|
+
"advanced",
|
|
142
|
+
({ variable: v, selector: s, css }) => {
|
|
143
|
+
v("spacing-unit", "8px");
|
|
144
|
+
v("primary-hue", "240");
|
|
145
|
+
|
|
146
|
+
s(".card", ({ selector: nested }) => {
|
|
147
|
+
nested("&:hover", {
|
|
148
|
+
transform: "translateY(-2px)",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
nested(".card-header", {
|
|
152
|
+
backgroundColor: css`hsl(${ref("primary-hue")}, 70%, 90%)`,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
padding: ref("spacing-unit"),
|
|
157
|
+
borderRadius: "4px",
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const result = consumeTheme(complexTheme, options);
|
|
164
|
+
expect(result).toEqual(`[data-theme="advanced"] {
|
|
165
|
+
\t--spacing-unit: 8px;
|
|
166
|
+
\t--primary-hue: 240;
|
|
167
|
+
\t
|
|
168
|
+
\t.card {
|
|
169
|
+
\t\tpadding: var(--spacing-unit);
|
|
170
|
+
\t\tborder-radius: 4px;
|
|
171
|
+
\t\t
|
|
172
|
+
\t\t&:hover {
|
|
173
|
+
\t\t\ttransform: translateY(-2px);
|
|
174
|
+
\t\t}
|
|
175
|
+
\t\t
|
|
176
|
+
\t\t.card-header {
|
|
177
|
+
\t\t\tbackground-color: hsl(var(--primary-hue), 70%, 90%);
|
|
178
|
+
\t\t}
|
|
179
|
+
\t}
|
|
180
|
+
}`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle themes with CSS variable references", () => {
|
|
184
|
+
const baseVar = variable("base-size", "16px");
|
|
185
|
+
const refTheme = theme("referenced", ({ variable: v }) => {
|
|
186
|
+
v("large-size", css`calc(${ref(baseVar)} * 1.5)`);
|
|
187
|
+
v("small-size", css`calc(${ref(baseVar)} * 0.875)`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const result = consumeTheme(refTheme, options);
|
|
191
|
+
expect(result).toEqual(`[data-theme="referenced"] {
|
|
192
|
+
\t--large-size: calc(var(--base-size) * 1.5);
|
|
193
|
+
\t--small-size: calc(var(--base-size) * 0.875);
|
|
194
|
+
}`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should handle themes with both custom selector and variable prefix", () => {
|
|
198
|
+
const combinedOptions: StyleframeOptions = {
|
|
199
|
+
theme: {
|
|
200
|
+
selector: ({ name }) => `#theme-${name}`,
|
|
201
|
+
},
|
|
202
|
+
variables: {
|
|
203
|
+
name: ({ name }) => `--ui-${name}`,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const combinedTheme = theme("custom", ({ variable: v }) => {
|
|
208
|
+
v("text-color", "#333333");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(consumeTheme(combinedTheme, combinedOptions)).toBe(
|
|
212
|
+
"#theme-custom {\n\t--ui-text-color: #333333;\n}",
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StyleframeOptions, Theme } from "@styleframe/core";
|
|
2
|
+
import type { ConsumeFunction } from "../types";
|
|
3
|
+
import { createContainerConsumer } from "./container";
|
|
4
|
+
import { defaultThemeSelectorFn } from "../defaults";
|
|
5
|
+
|
|
6
|
+
export function createThemeConsumer(consume: ConsumeFunction) {
|
|
7
|
+
const consumeContainer = createContainerConsumer(consume);
|
|
8
|
+
|
|
9
|
+
return function consumeTheme(instance: Theme, options: StyleframeOptions) {
|
|
10
|
+
const selectorFn = options.theme?.selector ?? defaultThemeSelectorFn;
|
|
11
|
+
const query = selectorFn({ name: instance.name });
|
|
12
|
+
|
|
13
|
+
return consumeContainer(query, instance, options);
|
|
14
|
+
};
|
|
15
|
+
}
|