clava 0.3.0 → 0.4.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 +66 -0
- package/README.md +38 -38
- package/dist/index.d.ts +17 -25
- package/dist/index.js +94 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +187 -149
- package/src/types.ts +36 -44
- package/tests/_utils.ts +1 -3
- package/tests/component-api.test.ts +28 -28
- package/tests/extend.test.ts +6 -6
- 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} +145 -151
- package/tests/variants-inference.test.ts +252 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# clava
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Removed `computedVariants` in favor of function values in `variants`
|
|
6
|
+
|
|
7
|
+
**BREAKING** if you're using the `computedVariants` config option.
|
|
8
|
+
|
|
9
|
+
Define a function directly inside [`variants`](https://clava.style/docs/reference/cv#variants) — it now acts as a function variant. The function's parameter type defines the prop type and replaces any inherited variant for the same key.
|
|
10
|
+
|
|
11
|
+
Before:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const grid = cv({
|
|
15
|
+
variants: {
|
|
16
|
+
color: { red: "text-red", blue: "text-blue" },
|
|
17
|
+
},
|
|
18
|
+
computedVariants: {
|
|
19
|
+
columns: (value: number) => `grid-cols-${value}`,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const grid = cv({
|
|
28
|
+
variants: {
|
|
29
|
+
color: { red: "text-red", blue: "text-blue" },
|
|
30
|
+
columns: (value: number) => `grid-cols-${value}`,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Renamed `computed` to `refine`
|
|
36
|
+
|
|
37
|
+
**BREAKING** if you use the `computed` config field on [`cv`](https://clava.style/docs/reference/cv).
|
|
38
|
+
|
|
39
|
+
The `computed` field previously collided with `computedVariants`, even though the two have very different semantics: `computedVariants` is a map of pure per-variant transformer functions, while `computed` is a single imperative callback that can mutate variants, set defaults, and emit class/style output across re-runs until variants stabilize. The new name `refine` describes that iterative refinement and removes the collision.
|
|
40
|
+
|
|
41
|
+
Rename the `computed` field to `refine`. The callback signature and context (`variants`, `setVariants`, `setDefaultVariants`, `addClass`, `addStyle`) are unchanged.
|
|
42
|
+
|
|
43
|
+
Before:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const button = cv({
|
|
47
|
+
variants: { size: { sm: "sm", lg: "lg" } },
|
|
48
|
+
computed: ({ variants, addClass }) => {
|
|
49
|
+
if (variants.size === "lg") {
|
|
50
|
+
addClass("is-large");
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
After:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const button = cv({
|
|
60
|
+
variants: { size: { sm: "sm", lg: "lg" } },
|
|
61
|
+
refine: ({ variants, addClass }) => {
|
|
62
|
+
if (variants.size === "lg") {
|
|
63
|
+
addClass("is-large");
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
3
69
|
## 0.3.0
|
|
4
70
|
|
|
5
71
|
### Removed `keys`
|
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ import type { Variant, VariantProps } from "clava";
|
|
|
16
16
|
- [Output Modes](#output-modes)
|
|
17
17
|
- [Classes And Styles](#classes-and-styles)
|
|
18
18
|
- [Variants](#variants)
|
|
19
|
+
- [Function Variants](#function-variants)
|
|
19
20
|
- [Extending Components](#extending-components)
|
|
20
|
-
- [
|
|
21
|
-
- [Computed Logic](#computed-logic)
|
|
21
|
+
- [Refine](#refine)
|
|
22
22
|
- [Splitting Props](#splitting-props)
|
|
23
23
|
- [React](#react)
|
|
24
24
|
- [Solid](#solid)
|
|
@@ -78,7 +78,7 @@ button({ size: "lg", disabled: true, fluid: true, className: "mt-2" });
|
|
|
78
78
|
// }
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Variant prop types are inferred from the `variants`, `
|
|
81
|
+
Variant prop types are inferred from the `variants`, `defaultVariants`, and `extend` configuration. Invalid variant keys and values are TypeScript errors and are ignored at runtime.
|
|
82
82
|
|
|
83
83
|
Input props may use `class` or `className` in any output mode. Both are appended to the generated class string.
|
|
84
84
|
|
|
@@ -152,7 +152,7 @@ const card = cv({
|
|
|
152
152
|
});
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
Style results from `variants` and `refine` must use an explicit `{ style }` wrapper. A raw object like `{ backgroundColor: "red" }` is not a style result by itself.
|
|
156
156
|
|
|
157
157
|
```ts
|
|
158
158
|
const chip = cv({
|
|
@@ -226,7 +226,34 @@ item({ active: true, interactive: true }).class;
|
|
|
226
226
|
// "item-active item-interactive focus-visible:ring"
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
Variant values can be class values, arrays,
|
|
229
|
+
Variant values can be class values, arrays, `{ class, style }` objects, or [functions](#function-variants). Use `null` in an extending component to disable inherited variants or inherited variant values.
|
|
230
|
+
|
|
231
|
+
## Function Variants
|
|
232
|
+
|
|
233
|
+
Add a function as a variant value when the prop should generate class/style output dynamically. The function's parameter type defines the prop type.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const grid = cv({
|
|
237
|
+
class: "grid",
|
|
238
|
+
variants: {
|
|
239
|
+
columns: (value: number) => ({
|
|
240
|
+
class: `grid-cols-${value}`,
|
|
241
|
+
style: { "--grid-columns": `${value}` },
|
|
242
|
+
}),
|
|
243
|
+
color: (value: string | null) => {
|
|
244
|
+
return value ? `text-${value}` : "text-current";
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
grid({ columns: 3, color: null });
|
|
250
|
+
// {
|
|
251
|
+
// class: "grid grid-cols-3 text-current",
|
|
252
|
+
// style: { "--grid-columns": "3" },
|
|
253
|
+
// }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Function variants can return any class value, `{ class, style }`, a default Clava component result, `null`, or `undefined`. A function variant with the same key as an extended variant replaces that inherited variant's prop type and output.
|
|
230
257
|
|
|
231
258
|
## Extending Components
|
|
232
259
|
|
|
@@ -287,36 +314,9 @@ const plainButton = cv({
|
|
|
287
314
|
|
|
288
315
|
You can extend any component mode, including `baseButton.jsx`, `baseButton.html`, and `baseButton.htmlObj`.
|
|
289
316
|
|
|
290
|
-
##
|
|
291
|
-
|
|
292
|
-
Use `computedVariants` when a prop value should generate class/style output dynamically. The function parameter defines the prop type.
|
|
293
|
-
|
|
294
|
-
```ts
|
|
295
|
-
const grid = cv({
|
|
296
|
-
class: "grid",
|
|
297
|
-
computedVariants: {
|
|
298
|
-
columns: (value: number) => ({
|
|
299
|
-
class: `grid-cols-${value}`,
|
|
300
|
-
style: { "--grid-columns": `${value}` },
|
|
301
|
-
}),
|
|
302
|
-
color: (value: string | null) => {
|
|
303
|
-
return value ? `text-${value}` : "text-current";
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
grid({ columns: 3, color: null });
|
|
309
|
-
// {
|
|
310
|
-
// class: "grid grid-cols-3 text-current",
|
|
311
|
-
// style: { "--grid-columns": "3" },
|
|
312
|
-
// }
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Computed variants can return any class value, `{ class, style }`, a default Clava component result, `null`, or `undefined`. A `computedVariants` entry with the same key as an extended variant replaces that inherited variant's prop type and output.
|
|
316
|
-
|
|
317
|
-
## Computed Logic
|
|
317
|
+
## Refine
|
|
318
318
|
|
|
319
|
-
Use `
|
|
319
|
+
Use `refine` for compound conditions, dependent defaults, and final class/style adjustments. It receives the resolved variant values for the component and can return class/style output.
|
|
320
320
|
|
|
321
321
|
```ts
|
|
322
322
|
const toolbarButton = cv({
|
|
@@ -328,7 +328,7 @@ const toolbarButton = cv({
|
|
|
328
328
|
},
|
|
329
329
|
loading: "toolbar-button-loading",
|
|
330
330
|
},
|
|
331
|
-
|
|
331
|
+
refine: ({
|
|
332
332
|
variants,
|
|
333
333
|
setVariants,
|
|
334
334
|
setDefaultVariants,
|
|
@@ -355,7 +355,7 @@ const toolbarButton = cv({
|
|
|
355
355
|
|
|
356
356
|
`setVariants()` overrides explicit props. `setDefaultVariants()` overrides static `defaultVariants` and inherited defaults, but it does not override a prop the user explicitly passed unless that prop value is `undefined`. `addClass()` and `addStyle()` append output without changing resolved variant values. `getVariants()` includes values changed by `setVariants()` and `setDefaultVariants()`.
|
|
357
357
|
|
|
358
|
-
When a
|
|
358
|
+
When a `refine` callback changes variants, Clava re-runs the refine chain so later reads see the latest values. Re-runs are capped at 50 iterations, after which Clava stops and logs a warning in development.
|
|
359
359
|
|
|
360
360
|
## Splitting Props
|
|
361
361
|
|
|
@@ -525,7 +525,7 @@ The package also exports `ClassValue`, `StyleValue`, `StyleClassProps`, `StyleCl
|
|
|
525
525
|
|
|
526
526
|
## API Summary
|
|
527
527
|
|
|
528
|
-
`cv(config?)` creates a typed Clava component. Supported config keys are `extend`, `class`, `style`, `variants`, `
|
|
528
|
+
`cv(config?)` creates a typed Clava component. Supported config keys are `extend`, `class`, `style`, `variants`, `defaultVariants`, and `refine`.
|
|
529
529
|
|
|
530
530
|
`component(props?)` returns `{ class, style }` with normalized camelCase style keys.
|
|
531
531
|
|
|
@@ -539,7 +539,7 @@ The package also exports `ClassValue`, `StyleValue`, `StyleClassProps`, `StyleCl
|
|
|
539
539
|
|
|
540
540
|
`component.style(props?)` returns only the resolved style value for that component mode.
|
|
541
541
|
|
|
542
|
-
`component.getVariants(props?)` returns resolved variant values after static defaults, inherited defaults, and
|
|
542
|
+
`component.getVariants(props?)` returns resolved variant values after static defaults, inherited defaults, and `refine` updates.
|
|
543
543
|
|
|
544
544
|
`component.propKeys` lists style props plus variant props for that component mode.
|
|
545
545
|
|
package/dist/index.d.ts
CHANGED
|
@@ -61,21 +61,17 @@ interface ModalComponent<V, R extends ComponentResult> {
|
|
|
61
61
|
variantKeys: (keyof V)[];
|
|
62
62
|
propKeys: (keyof V | ComponentPropKey<R>)[];
|
|
63
63
|
}
|
|
64
|
-
interface CVComponent<V extends Variants = {},
|
|
65
|
-
jsx: ModalComponent<MergeVariants<V,
|
|
66
|
-
html: ModalComponent<MergeVariants<V,
|
|
67
|
-
htmlObj: ModalComponent<MergeVariants<V,
|
|
64
|
+
interface CVComponent<V extends Variants = {}, E extends AnyComponent[] = [], R extends ComponentResult = StyleClassProps> extends ModalComponent<MergeVariants<V, E>, R> {
|
|
65
|
+
jsx: ModalComponent<MergeVariants<V, E>, JSXProps>;
|
|
66
|
+
html: ModalComponent<MergeVariants<V, E>, HTMLProps>;
|
|
67
|
+
htmlObj: ModalComponent<MergeVariants<V, E>, HTMLObjProps>;
|
|
68
68
|
}
|
|
69
|
-
type AnyComponent = CVComponent<any, any, any
|
|
69
|
+
type AnyComponent = CVComponent<any, any, any> | ModalComponent<any, any>;
|
|
70
70
|
type MergeExtendedVariants<T> = T extends readonly [infer First, ...infer Rest] ? ExtractVariants<First> & MergeExtendedVariants<Rest> : {};
|
|
71
|
-
type
|
|
72
|
-
type
|
|
73
|
-
type ExtractComputedVariants<T> = T extends CVComponent<any, infer CV, infer E, any> ? CV & Omit<MergeExtendedComputedVariants<E>, keyof CV> : {};
|
|
74
|
-
type MergeVariantDefinition<Child, Parent> = Child extends Record<string, any> ? Parent extends Record<string, any> ? Omit<Parent, keyof Child> & Child : Child : Child;
|
|
71
|
+
type ExtractVariants<T> = T extends CVComponent<infer V, infer E, any> ? MergeVariantMaps<V, MergeExtendedVariants<E>> : {};
|
|
72
|
+
type MergeVariantDefinition<Child, Parent> = Child extends ((...args: any[]) => any) ? Child : Parent extends ((...args: any[]) => any) ? Child : Child extends Record<string, any> ? Parent extends Record<string, any> ? Omit<Parent, keyof Child> & Child : Child : Child;
|
|
75
73
|
type MergeVariantMaps<Child, Parent> = Omit<Parent, keyof Child> & Child & { [K in keyof Child & keyof Parent]: MergeVariantDefinition<Child[K], Parent[K]> };
|
|
76
|
-
type
|
|
77
|
-
type MergeBaseVariants<V, E extends AnyComponent[]> = MergeVariantMaps<NoInfer<V>, MergeExtendedAllVariants<E>>;
|
|
78
|
-
type MergeVariants<V, CV, E extends AnyComponent[]> = NoInfer<CV> & Omit<MergeBaseVariants<V, E>, keyof CV>;
|
|
74
|
+
type MergeVariants<V, E extends AnyComponent[]> = MergeVariantMaps<NoInfer<V>, MergeExtendedVariants<E>>;
|
|
79
75
|
type StringToBoolean<T> = T extends "true" | "false" ? boolean : T;
|
|
80
76
|
type VariantValue = ClassValue | StyleClassValue;
|
|
81
77
|
type NonNullKeys<T> = { [K in keyof T]: T[K] extends null ? never : K }[keyof T];
|
|
@@ -88,34 +84,30 @@ interface StyleClassValue {
|
|
|
88
84
|
style?: StyleValue;
|
|
89
85
|
class?: ClassValue;
|
|
90
86
|
}
|
|
91
|
-
interface
|
|
87
|
+
interface RefineContext<V> {
|
|
92
88
|
variants: VariantValues<V>;
|
|
93
89
|
setVariants: (variants: VariantValues<V>) => void;
|
|
94
90
|
setDefaultVariants: (variants: VariantValues<V>) => void;
|
|
95
91
|
addClass: (className: ClassValue) => void;
|
|
96
92
|
addStyle: (style: StyleValue) => void;
|
|
97
93
|
}
|
|
98
|
-
type
|
|
99
|
-
type
|
|
100
|
-
type ComputedVariants = Record<string, ComputedVariant>;
|
|
101
|
-
type Variant$1 = ClassValue | Record<string, VariantValue>;
|
|
94
|
+
type Refine<V> = (context: RefineContext<V>) => VariantValue;
|
|
95
|
+
type Variant$1 = ClassValue | Record<string, VariantValue> | ((value: any) => VariantValue);
|
|
102
96
|
type Variants = Record<string, Variant$1>;
|
|
103
|
-
type ExtendedVariants<E extends AnyComponent[]> = MergeExtendedVariants<E> & MergeExtendedComputedVariants<E>;
|
|
104
97
|
type NullablePartial<T> = T extends Record<string, any> ? { [K in keyof T]?: T[K] | null } : T | null;
|
|
105
|
-
type ExtendableVariants<V extends Variants, E extends AnyComponent[]> = V & { [K in keyof
|
|
98
|
+
type ExtendableVariants<V extends Variants, E extends AnyComponent[]> = V & { [K in keyof MergeExtendedVariants<E>]?: NullablePartial<MergeExtendedVariants<E>[K]> | Variant$1 };
|
|
106
99
|
//#endregion
|
|
107
100
|
//#region src/index.d.ts
|
|
108
101
|
type VariantProps<T extends Pick<AnyComponent, "getVariants">> = ReturnType<T["getVariants"]>;
|
|
109
102
|
type VariantKey<T> = T extends boolean ? "true" | "false" : Extract<T, string>;
|
|
110
103
|
type Variant<T extends Pick<AnyComponent, "getVariants">, K extends keyof VariantProps<T>> = Record<VariantKey<NonNullable<VariantProps<T>[K]>>, ClassValue | StyleClassValue>;
|
|
111
|
-
interface CVConfig<V extends Variants = {},
|
|
104
|
+
interface CVConfig<V extends Variants = {}, E extends AnyComponent[] = []> {
|
|
112
105
|
extend?: E;
|
|
113
106
|
class?: ClassValue;
|
|
114
107
|
style?: StyleValue;
|
|
115
108
|
variants?: ExtendableVariants<V, E>;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
computed?: Computed<MergeVariants<V, CV, E>>;
|
|
109
|
+
defaultVariants?: VariantValues<MergeVariants<V, E>>;
|
|
110
|
+
refine?: Refine<MergeVariants<V, E>>;
|
|
119
111
|
}
|
|
120
112
|
interface CreateParams {
|
|
121
113
|
transformClass?: (className: string) => string;
|
|
@@ -135,10 +127,10 @@ declare const splitProps: SplitPropsFunction;
|
|
|
135
127
|
declare function create({
|
|
136
128
|
transformClass
|
|
137
129
|
}?: CreateParams): {
|
|
138
|
-
cv: <V extends Variants = {},
|
|
130
|
+
cv: <V extends Variants = {}, const E extends AnyComponent[] = []>(config?: CVConfig<V, E>) => CVComponent<V, E>;
|
|
139
131
|
cx: (...classes: ClassValue$1[]) => string;
|
|
140
132
|
};
|
|
141
|
-
declare const cv: <V extends Variants = {},
|
|
133
|
+
declare const cv: <V extends Variants = {}, const E extends AnyComponent[] = []>(config?: CVConfig<V, E>) => CVComponent<V, E>, cx: (...classes: ClassValue$1[]) => string;
|
|
142
134
|
//#endregion
|
|
143
135
|
export { type CVComponent, CVConfig, type ClassValue, type HTMLObjProps, type HTMLProps, type JSXProps, type StyleClassProps, type StyleClassValue, type StyleValue, Variant, VariantProps, create, cv, cx, splitProps };
|
|
144
136
|
//# sourceMappingURL=index.d.ts.map
|