clava 0.3.0 → 0.4.1
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/CHANGELOG.md +86 -0
- package/README.md +38 -38
- package/dist/index.d.ts +19 -27
- package/dist/index.js +143 -86
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/index.ts +430 -215
- package/src/types.ts +38 -46
- package/src/utils.ts +31 -10
- package/tests/_utils.ts +7 -5
- package/tests/build.test.ts +81 -7
- package/tests/component-api.test.ts +28 -28
- package/tests/extend.test.ts +13 -8
- package/tests/{computed-variants.test.ts → function-variants.test.ts} +105 -25
- package/tests/prototype-pollution.test.ts +6 -6
- package/tests/{computed.test.ts → refine.test.ts} +292 -149
- package/tests/variants-inference.test.ts +252 -0
|
@@ -15,11 +15,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
15
15
|
const cls = getConfigTransformClass(config);
|
|
16
16
|
|
|
17
17
|
describe(getConfigDescription(config), () => {
|
|
18
|
-
test("
|
|
18
|
+
test("function variant", () => {
|
|
19
19
|
const component = getModeComponent(
|
|
20
20
|
mode,
|
|
21
21
|
cv({
|
|
22
|
-
|
|
22
|
+
variants: {
|
|
23
23
|
size: (value: "sm" | "lg") => (value === "sm" ? "small" : "large"),
|
|
24
24
|
},
|
|
25
25
|
}),
|
|
@@ -28,11 +28,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
28
28
|
expect(getStyleClass(props)).toEqual({ class: cls("large") });
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("
|
|
31
|
+
test("function variant with style", () => {
|
|
32
32
|
const component = getModeComponent(
|
|
33
33
|
mode,
|
|
34
34
|
cv({
|
|
35
|
-
|
|
35
|
+
variants: {
|
|
36
36
|
size: (value: "sm" | "lg") => ({
|
|
37
37
|
class: value === "sm" ? "small" : "large",
|
|
38
38
|
style: { fontSize: value === "sm" ? "12px" : "16px" },
|
|
@@ -47,7 +47,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
test("
|
|
50
|
+
test("function variant can return another component default result", () => {
|
|
51
51
|
const button = cv({
|
|
52
52
|
variants: {
|
|
53
53
|
size: {
|
|
@@ -59,7 +59,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
59
59
|
const component = getModeComponent(
|
|
60
60
|
mode,
|
|
61
61
|
cv({
|
|
62
|
-
|
|
62
|
+
variants: {
|
|
63
63
|
size: (value: "sm" | "lg") => {
|
|
64
64
|
return button({ size: value });
|
|
65
65
|
},
|
|
@@ -73,7 +73,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("
|
|
76
|
+
test("function variant overrides extended object variant", () => {
|
|
77
77
|
const base = cv({
|
|
78
78
|
variants: {
|
|
79
79
|
size: {
|
|
@@ -86,7 +86,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
86
86
|
mode,
|
|
87
87
|
cv({
|
|
88
88
|
extend: [base],
|
|
89
|
-
|
|
89
|
+
variants: {
|
|
90
90
|
size: (value: "sm" | "lg") => ({
|
|
91
91
|
class: value === "sm" ? "extended-sm" : "extended-lg",
|
|
92
92
|
style: {
|
|
@@ -103,9 +103,9 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test("
|
|
106
|
+
test("function variant overrides extended function variant", () => {
|
|
107
107
|
const base = cv({
|
|
108
|
-
|
|
108
|
+
variants: {
|
|
109
109
|
size: (value: "sm" | "lg") => ({
|
|
110
110
|
class: value === "sm" ? "base-sm" : "base-lg",
|
|
111
111
|
style: { fontSize: value === "sm" ? "12px" : "16px" },
|
|
@@ -116,7 +116,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
116
116
|
mode,
|
|
117
117
|
cv({
|
|
118
118
|
extend: [base],
|
|
119
|
-
|
|
119
|
+
variants: {
|
|
120
120
|
size: (value: "sm" | "lg") => ({
|
|
121
121
|
class: value === "sm" ? "extended-sm" : "extended-lg",
|
|
122
122
|
style: {
|
|
@@ -133,11 +133,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
test("
|
|
136
|
+
test("function variant style does not accept numbers", () => {
|
|
137
137
|
const component = getModeComponent(
|
|
138
138
|
mode,
|
|
139
139
|
cv({
|
|
140
|
-
|
|
140
|
+
variants: {
|
|
141
141
|
// @ts-expect-error
|
|
142
142
|
size: (value: "sm" | "lg") => ({
|
|
143
143
|
class: value === "sm" ? "small" : "large",
|
|
@@ -153,7 +153,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
test("
|
|
156
|
+
test("function variant changes extended boolean variant to string", () => {
|
|
157
157
|
const base = cv({
|
|
158
158
|
variants: { disabled: { true: "disabled", false: "enabled" } },
|
|
159
159
|
});
|
|
@@ -161,7 +161,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
161
161
|
mode,
|
|
162
162
|
cv({
|
|
163
163
|
extend: [base],
|
|
164
|
-
|
|
164
|
+
variants: {
|
|
165
165
|
disabled: (value: "yes" | "no" | "maybe") => {
|
|
166
166
|
if (value === "yes") return "state-disabled";
|
|
167
167
|
if (value === "no") return "state-enabled";
|
|
@@ -178,13 +178,13 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
178
178
|
expect(getStyleClass(props)).toEqual({ class: cls("state-pending") });
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test("
|
|
181
|
+
test("function variant changes extended string variant to boolean", () => {
|
|
182
182
|
const base = cv({ variants: { size: { sm: "sm", md: "md", lg: "lg" } } });
|
|
183
183
|
const component = getModeComponent(
|
|
184
184
|
mode,
|
|
185
185
|
cv({
|
|
186
186
|
extend: [base],
|
|
187
|
-
|
|
187
|
+
variants: {
|
|
188
188
|
size: (value: boolean) => (value ? "size-large" : "size-small"),
|
|
189
189
|
},
|
|
190
190
|
}),
|
|
@@ -199,11 +199,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
199
199
|
expect(getStyleClass(propsFalse)).toEqual({ class: cls("size-small") });
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
test("
|
|
202
|
+
test("function variant with number type", () => {
|
|
203
203
|
const component = getModeComponent(
|
|
204
204
|
mode,
|
|
205
205
|
cv({
|
|
206
|
-
|
|
206
|
+
variants: {
|
|
207
207
|
columns: (value: number) => ({
|
|
208
208
|
class: `grid-cols-${value}`,
|
|
209
209
|
style: { "--grid-columns": `${value}` },
|
|
@@ -218,11 +218,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
218
218
|
});
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
test("
|
|
221
|
+
test("function variant with number type returns dynamic styles", () => {
|
|
222
222
|
const component = getModeComponent(
|
|
223
223
|
mode,
|
|
224
224
|
cv({
|
|
225
|
-
|
|
225
|
+
variants: {
|
|
226
226
|
gap: (value: number) => ({
|
|
227
227
|
style: { "--gap": `${value * 4}px` },
|
|
228
228
|
}),
|
|
@@ -244,11 +244,11 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
244
244
|
});
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
test("
|
|
247
|
+
test("function variant with nullable type", () => {
|
|
248
248
|
const component = getModeComponent(
|
|
249
249
|
mode,
|
|
250
250
|
cv({
|
|
251
|
-
|
|
251
|
+
variants: {
|
|
252
252
|
color: (value: string | null) => ({
|
|
253
253
|
class: value ? `color-${value}` : "color-default",
|
|
254
254
|
style: { "--color": value ?? "inherit" },
|
|
@@ -268,7 +268,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
268
268
|
});
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
-
test("
|
|
271
|
+
test("function variant changes extended variant from string to number", () => {
|
|
272
272
|
const base = cv({
|
|
273
273
|
variants: { size: { sm: "text-sm", md: "text-md", lg: "text-lg" } },
|
|
274
274
|
});
|
|
@@ -276,7 +276,7 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
276
276
|
mode,
|
|
277
277
|
cv({
|
|
278
278
|
extend: [base],
|
|
279
|
-
|
|
279
|
+
variants: {
|
|
280
280
|
size: (value: number) => ({
|
|
281
281
|
class: "text-custom",
|
|
282
282
|
style: { fontSize: `${value}px` },
|
|
@@ -294,5 +294,85 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
294
294
|
fontSize: "18px",
|
|
295
295
|
});
|
|
296
296
|
});
|
|
297
|
+
|
|
298
|
+
test("static and function variants combine within the same component", () => {
|
|
299
|
+
const component = getModeComponent(
|
|
300
|
+
mode,
|
|
301
|
+
cv({
|
|
302
|
+
variants: {
|
|
303
|
+
size: { sm: "size-sm", lg: "size-lg" },
|
|
304
|
+
columns: (value: number) => `cols-${value}`,
|
|
305
|
+
},
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
const props = component({ size: "lg", columns: 3 });
|
|
309
|
+
expect(getStyleClass(props)).toEqual({
|
|
310
|
+
class: cls("size-lg cols-3"),
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("object variant in child replaces extended function variant at runtime", () => {
|
|
315
|
+
const base = cv({
|
|
316
|
+
variants: {
|
|
317
|
+
// The function is typed for `number`; if it ran with a child string,
|
|
318
|
+
// it would emit garbage like `base-fn-sm`. The merged-variant type
|
|
319
|
+
// says the child's object replaces the parent function entirely, so
|
|
320
|
+
// the runtime must skip the parent's function for this key.
|
|
321
|
+
size: (value: number) => `base-fn-${value}`,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
const component = getModeComponent(
|
|
325
|
+
mode,
|
|
326
|
+
cv({
|
|
327
|
+
extend: [base],
|
|
328
|
+
variants: {
|
|
329
|
+
size: { sm: "child-sm", lg: "child-lg" },
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
const propsSm = component({ size: "sm" });
|
|
334
|
+
expect(getStyleClass(propsSm)).toEqual({ class: cls("child-sm") });
|
|
335
|
+
const propsLg = component({ size: "lg" });
|
|
336
|
+
expect(getStyleClass(propsLg)).toEqual({ class: cls("child-lg") });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("object variant in grandchild replaces grandparent function variant at runtime", () => {
|
|
340
|
+
const base = cv({
|
|
341
|
+
variants: { size: (value: number) => `base-fn-${value}` },
|
|
342
|
+
});
|
|
343
|
+
const middle = cv({ extend: [base], class: "middle" });
|
|
344
|
+
const component = getModeComponent(
|
|
345
|
+
mode,
|
|
346
|
+
cv({
|
|
347
|
+
extend: [middle],
|
|
348
|
+
variants: { size: { sm: "gc-sm", lg: "gc-lg" } },
|
|
349
|
+
}),
|
|
350
|
+
);
|
|
351
|
+
const props = component({ size: "sm" });
|
|
352
|
+
expect(getStyleClass(props)).toEqual({ class: cls("middle gc-sm") });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("intermediate object variant hides grandparent function from later extends", () => {
|
|
356
|
+
const base = cv({
|
|
357
|
+
variants: { size: (value: number) => `base-fn-${value}` },
|
|
358
|
+
});
|
|
359
|
+
const middle = cv({
|
|
360
|
+
extend: [base],
|
|
361
|
+
variants: { size: { sm: "middle-sm", md: "middle-md" } },
|
|
362
|
+
});
|
|
363
|
+
const component = getModeComponent(
|
|
364
|
+
mode,
|
|
365
|
+
cv({
|
|
366
|
+
extend: [middle],
|
|
367
|
+
variants: { size: { sm: "child-sm", lg: "child-lg" } },
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
// Middle already replaced the grandparent function with an object, so
|
|
371
|
+
// child sees only objects in the chain — both objects merge by value.
|
|
372
|
+
const props = component({ size: "sm" });
|
|
373
|
+
expect(getStyleClass(props)).toEqual({
|
|
374
|
+
class: cls("middle-sm child-sm"),
|
|
375
|
+
});
|
|
376
|
+
});
|
|
297
377
|
});
|
|
298
378
|
}
|
|
@@ -27,16 +27,16 @@ test("render ignores keys inherited from Object.prototype", () => {
|
|
|
27
27
|
expect(component({}).class).toBe("sm");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
test("extend's resolveDefaults ignores polluted prototype on parent's
|
|
31
|
-
// Base's
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
30
|
+
test("extend's resolveDefaults ignores polluted prototype on parent's refine", () => {
|
|
31
|
+
// Base's refine branches on its own variants.size — if the polluted "size"
|
|
32
|
+
// key leaks into resolveDefaultsFn's resolvedVariants, the refine callback
|
|
33
|
+
// would see size = "lg" instead of the staticDefault "sm" and emit the
|
|
34
|
+
// lg-specific class.
|
|
35
35
|
proto.size = "lg";
|
|
36
36
|
const base = cv({
|
|
37
37
|
variants: { size: { sm: "sm", lg: "lg" } },
|
|
38
38
|
defaultVariants: { size: "sm" },
|
|
39
|
-
|
|
39
|
+
refine: ({ variants, addClass }) => {
|
|
40
40
|
if (variants.size === "lg") {
|
|
41
41
|
addClass("base-lg-detected");
|
|
42
42
|
}
|