clava 0.2.3 → 0.3.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/CHANGELOG.md +28 -0
- package/README.md +552 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +310 -141
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rolldown.config.ts +1 -0
- package/src/index.ts +600 -205
- package/src/types.ts +15 -10
- package/tests/build.test.ts +18 -0
- package/tests/component-api.test.ts +276 -7
- package/tests/computed.test.ts +424 -1
- package/tests/solid.test.ts +30 -0
- package/tests/split-props.test.ts +73 -1
- package/tests/variants.test.ts +29 -1
package/src/types.ts
CHANGED
|
@@ -70,11 +70,15 @@ export type ComponentProps<V = {}> = VariantValues<V> & NullableComponentResult;
|
|
|
70
70
|
|
|
71
71
|
export type GetVariants<V> = (variants?: VariantValues<V>) => VariantValues<V>;
|
|
72
72
|
|
|
73
|
+
type ComponentPropKey<R extends ComponentResult> =
|
|
74
|
+
| keyof R
|
|
75
|
+
| (R extends StyleClassProps ? "className" : never);
|
|
76
|
+
|
|
73
77
|
// Key source types - what can be passed as additional parameters to splitProps
|
|
74
78
|
export type KeySourceArray = readonly string[];
|
|
75
79
|
export type KeySourceComponent = {
|
|
76
|
-
|
|
77
|
-
variantKeys: readonly
|
|
80
|
+
propKeys: readonly string[];
|
|
81
|
+
variantKeys: readonly string[];
|
|
78
82
|
getVariants: () => Record<string, unknown>;
|
|
79
83
|
};
|
|
80
84
|
export type KeySource = KeySourceArray | KeySourceComponent;
|
|
@@ -85,7 +89,7 @@ type IsComponent<S> = S extends { getVariants: () => unknown } ? true : false;
|
|
|
85
89
|
// Extract keys from a source (includes class/style for components)
|
|
86
90
|
type SourceKeys<S> = S extends readonly (infer K)[]
|
|
87
91
|
? K
|
|
88
|
-
: S extends {
|
|
92
|
+
: S extends { propKeys: readonly (infer K)[] }
|
|
89
93
|
? K
|
|
90
94
|
: never;
|
|
91
95
|
|
|
@@ -169,9 +173,8 @@ export interface ModalComponent<V, R extends ComponentResult> {
|
|
|
169
173
|
class: (props?: ComponentProps<V>) => string;
|
|
170
174
|
style: (props?: ComponentProps<V>) => R["style"];
|
|
171
175
|
getVariants: GetVariants<V>;
|
|
172
|
-
keys: (keyof V | keyof NullableComponentResult)[];
|
|
173
176
|
variantKeys: (keyof V)[];
|
|
174
|
-
propKeys: (keyof V |
|
|
177
|
+
propKeys: (keyof V | ComponentPropKey<R>)[];
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
export interface CVComponent<
|
|
@@ -248,11 +251,13 @@ type ExtractVariantValue<T> = T extends null
|
|
|
248
251
|
? never
|
|
249
252
|
: T extends (value: infer V) => any
|
|
250
253
|
? V
|
|
251
|
-
: T extends
|
|
252
|
-
?
|
|
253
|
-
: T extends
|
|
254
|
-
?
|
|
255
|
-
:
|
|
254
|
+
: T extends readonly unknown[]
|
|
255
|
+
? boolean
|
|
256
|
+
: T extends Record<string, any>
|
|
257
|
+
? StringToBoolean<NonNullKeys<T>>
|
|
258
|
+
: T extends ClassValue
|
|
259
|
+
? boolean
|
|
260
|
+
: never;
|
|
256
261
|
|
|
257
262
|
export type VariantValues<V> = {
|
|
258
263
|
[K in keyof V]?: ExtractVariantValue<V[K]>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { expect, test } from "vitest";
|
|
7
|
+
|
|
8
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const exec = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
test("build preserves the production warning guard for consumers", async () => {
|
|
12
|
+
await exec("pnpm", ["--dir", root, "build"]);
|
|
13
|
+
|
|
14
|
+
const code = await readFile(join(root, "dist/index.js"), "utf8");
|
|
15
|
+
expect(code).toMatch(
|
|
16
|
+
/process\.env\.NODE_ENV !== "production"[\s\S]*?console\.warn\(/,
|
|
17
|
+
);
|
|
18
|
+
}, 60_000);
|
|
@@ -78,6 +78,48 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
78
78
|
expect(variants).toEqual({ size: "lg", color: "red" });
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
test("getVariants re-runs when computed changes variants", () => {
|
|
82
|
+
const component = getModeComponent(
|
|
83
|
+
mode,
|
|
84
|
+
cv({
|
|
85
|
+
variants: {
|
|
86
|
+
size: { sm: "sm", lg: "lg" },
|
|
87
|
+
color: { red: "red", blue: "blue" },
|
|
88
|
+
},
|
|
89
|
+
computed: ({ variants, setVariants }) => {
|
|
90
|
+
if (variants.size === "lg") {
|
|
91
|
+
setVariants({ color: "red" });
|
|
92
|
+
}
|
|
93
|
+
if (variants.color === "red") {
|
|
94
|
+
setVariants({ size: "sm" });
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
const variants = component.getVariants({ size: "lg" });
|
|
100
|
+
expect(variants).toEqual({ size: "sm", color: "red" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("getVariants re-runs when setDefaultVariants changes variants", () => {
|
|
104
|
+
const component = getModeComponent(
|
|
105
|
+
mode,
|
|
106
|
+
cv({
|
|
107
|
+
variants: {
|
|
108
|
+
size: { sm: "sm", lg: "lg" },
|
|
109
|
+
color: { red: "red", blue: "blue" },
|
|
110
|
+
},
|
|
111
|
+
computed: ({ variants, setDefaultVariants }) => {
|
|
112
|
+
setDefaultVariants({ color: "red" });
|
|
113
|
+
if (variants.color === "red") {
|
|
114
|
+
setDefaultVariants({ size: "lg" });
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
const variants = component.getVariants();
|
|
120
|
+
expect(variants).toEqual({ size: "lg", color: "red" });
|
|
121
|
+
});
|
|
122
|
+
|
|
81
123
|
test("getVariants returns variants set by computed setDefaultVariants", () => {
|
|
82
124
|
const component = getModeComponent(
|
|
83
125
|
mode,
|
|
@@ -170,17 +212,220 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
170
212
|
expect(variants).toEqual({ size: "lg", color: "red" });
|
|
171
213
|
});
|
|
172
214
|
|
|
173
|
-
test("
|
|
215
|
+
test("getVariants re-runs when base component computed changes variants", () => {
|
|
216
|
+
const base = cv({
|
|
217
|
+
variants: { size: { sm: "sm", lg: "lg" }, active: "" },
|
|
218
|
+
defaultVariants: { size: "sm" },
|
|
219
|
+
computed: ({ variants, setVariants }) => {
|
|
220
|
+
if (variants.active) {
|
|
221
|
+
setVariants({ size: "lg" });
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
174
225
|
const component = getModeComponent(
|
|
175
226
|
mode,
|
|
176
|
-
cv({
|
|
227
|
+
cv({
|
|
228
|
+
extend: [base],
|
|
229
|
+
variants: { color: { red: "red", blue: "blue" } },
|
|
230
|
+
computed: ({ variants, setVariants }) => {
|
|
231
|
+
if (variants.size === "lg") {
|
|
232
|
+
setVariants({ color: "red" });
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
177
236
|
);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
237
|
+
const variants = component.getVariants({ active: true });
|
|
238
|
+
expect(variants).toEqual({ size: "lg", active: true, color: "red" });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("getVariants preserves base setDefaultVariants after its own setVariants re-run", () => {
|
|
242
|
+
const base = cv({
|
|
243
|
+
variants: {
|
|
244
|
+
size: { sm: "sm", lg: "lg" },
|
|
245
|
+
active: "",
|
|
246
|
+
mode: { on: "on" },
|
|
247
|
+
},
|
|
248
|
+
defaultVariants: { size: "sm" },
|
|
249
|
+
computed: ({ variants, setVariants, setDefaultVariants }) => {
|
|
250
|
+
if (variants.active) {
|
|
251
|
+
setVariants({ mode: "on" });
|
|
252
|
+
}
|
|
253
|
+
if (variants.mode === "on") {
|
|
254
|
+
setDefaultVariants({ size: "lg" });
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
const component = getModeComponent(mode, cv({ extend: [base] }));
|
|
259
|
+
const variants = component.getVariants({ active: true });
|
|
260
|
+
expect(variants).toEqual({ size: "lg", active: true, mode: "on" });
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("getVariants setVariants uses the latest pending value", () => {
|
|
264
|
+
const component = getModeComponent(
|
|
265
|
+
mode,
|
|
266
|
+
cv({
|
|
267
|
+
variants: { size: { sm: "sm", lg: "lg" } },
|
|
268
|
+
defaultVariants: { size: "sm" },
|
|
269
|
+
computed: ({ setVariants }) => {
|
|
270
|
+
setVariants({ size: "lg" });
|
|
271
|
+
setVariants({ size: "sm" });
|
|
272
|
+
},
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
const variants = component.getVariants();
|
|
276
|
+
expect(variants).toEqual({ size: "sm" });
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("getVariants child setVariants keeps overriding base setDefaultVariants across re-runs", () => {
|
|
280
|
+
const base = cv({
|
|
281
|
+
variants: { color: { red: "red", blue: "blue" } },
|
|
282
|
+
computed: ({ setDefaultVariants }) => {
|
|
283
|
+
setDefaultVariants({ color: "blue" });
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
const component = getModeComponent(
|
|
287
|
+
mode,
|
|
288
|
+
cv({
|
|
289
|
+
extend: [base],
|
|
290
|
+
variants: { size: { sm: "sm", lg: "lg" } },
|
|
291
|
+
defaultVariants: { size: "sm" },
|
|
292
|
+
computed: ({ variants, setVariants }) => {
|
|
293
|
+
if (variants.size === "sm") {
|
|
294
|
+
setVariants({ color: "red" });
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
const variants = component.getVariants();
|
|
300
|
+
expect(variants).toEqual({ color: "red", size: "sm" });
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("getVariants setVariants sticks across re-runs", () => {
|
|
304
|
+
const base = cv({
|
|
305
|
+
variants: { color: { red: "red", blue: "blue" } },
|
|
306
|
+
computed: ({ setDefaultVariants }) => {
|
|
307
|
+
setDefaultVariants({ color: "blue" });
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
const component = getModeComponent(
|
|
311
|
+
mode,
|
|
312
|
+
cv({
|
|
313
|
+
extend: [base],
|
|
314
|
+
variants: { color: { red: "red", blue: "blue" }, done: "" },
|
|
315
|
+
computed: ({ variants, setVariants }) => {
|
|
316
|
+
if (!variants.done) {
|
|
317
|
+
setVariants({ color: "red", done: true });
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
const variants = component.getVariants();
|
|
323
|
+
expect(variants).toEqual({ color: "red", done: true });
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("getVariants base setDefaultVariants can override child static defaults after a re-run", () => {
|
|
327
|
+
const base = cv({
|
|
328
|
+
variants: {
|
|
329
|
+
size: { sm: "sm", lg: "lg" },
|
|
330
|
+
active: "",
|
|
331
|
+
mode: { on: "on" },
|
|
332
|
+
},
|
|
333
|
+
computed: ({ variants, setVariants, setDefaultVariants }) => {
|
|
334
|
+
if (variants.active) {
|
|
335
|
+
setVariants({ mode: "on" });
|
|
336
|
+
}
|
|
337
|
+
if (variants.mode === "on") {
|
|
338
|
+
setDefaultVariants({ size: "lg" });
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const component = getModeComponent(
|
|
343
|
+
mode,
|
|
344
|
+
cv({ extend: [base], defaultVariants: { size: "sm" } }),
|
|
345
|
+
);
|
|
346
|
+
const variants = component.getVariants({ active: true });
|
|
347
|
+
expect(variants).toEqual({ size: "lg", active: true, mode: "on" });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("getVariants setVariants from earlier extends overrides setDefaultVariants from later extends", () => {
|
|
351
|
+
const first = cv({
|
|
352
|
+
variants: { color: { red: "first-red", blue: "first-blue" } },
|
|
353
|
+
computed: ({ setVariants }) => {
|
|
354
|
+
setVariants({ color: "red" });
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
const second = cv({
|
|
358
|
+
variants: { color: { red: "second-red", blue: "second-blue" } },
|
|
359
|
+
computed: ({ setDefaultVariants }) => {
|
|
360
|
+
setDefaultVariants({ color: "blue" });
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
const component = getModeComponent(mode, cv({ extend: [first, second] }));
|
|
364
|
+
const variants = component.getVariants();
|
|
365
|
+
expect(variants).toEqual({ color: "red" });
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("getVariants setDefaultVariants from later extends overrides setDefaultVariants from earlier extends", () => {
|
|
369
|
+
const first = cv({
|
|
370
|
+
variants: { color: { red: "first-red", blue: "first-blue" } },
|
|
371
|
+
computed: ({ setDefaultVariants }) => {
|
|
372
|
+
setDefaultVariants({ color: "red" });
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
const second = cv({
|
|
376
|
+
variants: { color: { red: "second-red", blue: "second-blue" } },
|
|
377
|
+
computed: ({ setDefaultVariants }) => {
|
|
378
|
+
setDefaultVariants({ color: "blue" });
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const component = getModeComponent(mode, cv({ extend: [first, second] }));
|
|
382
|
+
const variants = component.getVariants();
|
|
383
|
+
expect(variants).toEqual({ color: "blue" });
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("getVariants setDefaultVariants does not override stable setVariants on later passes", () => {
|
|
387
|
+
const base = cv({
|
|
388
|
+
variants: { color: { red: "base-red", blue: "base-blue" } },
|
|
389
|
+
computed: ({ setVariants }) => {
|
|
390
|
+
setVariants({ color: "red" });
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
const component = getModeComponent(
|
|
394
|
+
mode,
|
|
395
|
+
cv({
|
|
396
|
+
extend: [base],
|
|
397
|
+
variants: { color: { red: "child-red", blue: "child-blue" } },
|
|
398
|
+
computed: ({ variants, setDefaultVariants }) => {
|
|
399
|
+
if (variants.color === "red") {
|
|
400
|
+
setDefaultVariants({ color: "blue" });
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
const variants = component.getVariants();
|
|
406
|
+
expect(variants).toEqual({ color: "red" });
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("getVariants setDefaultVariants does not override setVariants from a previous pass", () => {
|
|
410
|
+
const component = getModeComponent(
|
|
411
|
+
mode,
|
|
412
|
+
cv({
|
|
413
|
+
variants: {
|
|
414
|
+
color: { red: "red", blue: "blue" },
|
|
415
|
+
done: "",
|
|
416
|
+
},
|
|
417
|
+
computed: ({ variants, setVariants, setDefaultVariants }) => {
|
|
418
|
+
if (!variants.done) {
|
|
419
|
+
setVariants({ color: "red", done: true });
|
|
420
|
+
}
|
|
421
|
+
if (variants.done) {
|
|
422
|
+
setDefaultVariants({ color: "blue" });
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
183
426
|
);
|
|
427
|
+
const variants = component.getVariants();
|
|
428
|
+
expect(variants).toEqual({ color: "red", done: true });
|
|
184
429
|
});
|
|
185
430
|
|
|
186
431
|
test("variantKeys property", () => {
|
|
@@ -217,6 +462,30 @@ for (const config of Object.values(CONFIGS)) {
|
|
|
217
462
|
});
|
|
218
463
|
}
|
|
219
464
|
|
|
465
|
+
test("propKeys are mode-specific", () => {
|
|
466
|
+
const component = cvBase({
|
|
467
|
+
variants: { size: { sm: "sm", md: "md" } },
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expectTypeOf(component.propKeys).toEqualTypeOf<
|
|
471
|
+
("class" | "className" | "style" | "size")[]
|
|
472
|
+
>();
|
|
473
|
+
expectTypeOf(component.jsx.propKeys).toEqualTypeOf<
|
|
474
|
+
("className" | "style" | "size")[]
|
|
475
|
+
>();
|
|
476
|
+
expectTypeOf(component.html.propKeys).toEqualTypeOf<
|
|
477
|
+
("class" | "style" | "size")[]
|
|
478
|
+
>();
|
|
479
|
+
expectTypeOf(component.htmlObj.propKeys).toEqualTypeOf<
|
|
480
|
+
("class" | "style" | "size")[]
|
|
481
|
+
>();
|
|
482
|
+
|
|
483
|
+
expect(component.propKeys).toEqual(["class", "className", "style", "size"]);
|
|
484
|
+
expect(component.jsx.propKeys).toEqual(["className", "style", "size"]);
|
|
485
|
+
expect(component.html.propKeys).toEqual(["class", "style", "size"]);
|
|
486
|
+
expect(component.htmlObj.propKeys).toEqual(["class", "style", "size"]);
|
|
487
|
+
});
|
|
488
|
+
|
|
220
489
|
describe("Variant utility type", () => {
|
|
221
490
|
test("matches variant keys from another component", () => {
|
|
222
491
|
const base = cvBase({
|