clava 0.1.0 → 0.1.2
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 +12 -0
- package/dist/index.d.ts +25 -12
- package/dist/index.js +42 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +79 -51
- package/src/test-react.ts +40 -0
- package/src/test-solid.ts +36 -0
- package/src/test.ts +234 -310
- package/src/types.ts +131 -46
- package/src/utils.ts +15 -3
package/src/index.ts
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
import clsx, { type ClassValue as ClsxClassValue } from "clsx";
|
|
2
|
-
|
|
3
2
|
import type {
|
|
4
|
-
Variants,
|
|
5
|
-
ComputedVariants,
|
|
6
3
|
AnyComponent,
|
|
7
|
-
Component,
|
|
8
|
-
StyleProps,
|
|
9
4
|
ClassValue,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Component,
|
|
6
|
+
ComponentProps,
|
|
7
|
+
ComponentResult,
|
|
13
8
|
Computed,
|
|
9
|
+
ComputedVariants,
|
|
14
10
|
ExtendableVariants,
|
|
11
|
+
HTMLObjProps,
|
|
12
|
+
HTMLProps,
|
|
13
|
+
JSXProps,
|
|
15
14
|
MergeVariants,
|
|
16
15
|
ModalComponent,
|
|
17
|
-
ComponentResult,
|
|
18
|
-
JSXProps,
|
|
19
|
-
HTMLProps,
|
|
20
|
-
HTMLObjProps,
|
|
21
|
-
OnlyVariantsComponent,
|
|
22
|
-
ComponentProps,
|
|
23
16
|
SplitPropsFunction,
|
|
17
|
+
StyleClassValue,
|
|
18
|
+
StyleProps,
|
|
19
|
+
StyleValue,
|
|
20
|
+
VariantValues,
|
|
21
|
+
Variants,
|
|
24
22
|
} from "./types.ts";
|
|
25
|
-
|
|
26
23
|
import {
|
|
24
|
+
type Mode,
|
|
25
|
+
getClassPropertyName,
|
|
27
26
|
htmlObjStyleToStyleValue,
|
|
28
27
|
htmlStyleToStyleValue,
|
|
29
28
|
isHTMLObjStyle,
|
|
@@ -33,9 +32,6 @@ import {
|
|
|
33
32
|
styleValueToJSXStyle,
|
|
34
33
|
} from "./utils.ts";
|
|
35
34
|
|
|
36
|
-
const MODES = ["jsx", "html", "htmlObj"] as const;
|
|
37
|
-
type Mode = (typeof MODES)[number];
|
|
38
|
-
|
|
39
35
|
export type { ClassValue, StyleValue, StyleClassValue };
|
|
40
36
|
|
|
41
37
|
export type VariantProps<T extends Pick<AnyComponent, "getVariants">> =
|
|
@@ -124,7 +120,7 @@ function collectVariantKeys(
|
|
|
124
120
|
// Collect from extended components
|
|
125
121
|
if (config.extend) {
|
|
126
122
|
for (const ext of config.extend) {
|
|
127
|
-
for (const key of ext.
|
|
123
|
+
for (const key of ext.variantKeys) {
|
|
128
124
|
keys.add(key as string);
|
|
129
125
|
}
|
|
130
126
|
}
|
|
@@ -329,6 +325,7 @@ function processVariants(
|
|
|
329
325
|
function processComputed(
|
|
330
326
|
config: CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
331
327
|
resolvedVariants: Record<string, unknown>,
|
|
328
|
+
propsVariants: Record<string, unknown>,
|
|
332
329
|
): {
|
|
333
330
|
classes: ClassValue[];
|
|
334
331
|
style: StyleValue;
|
|
@@ -347,9 +344,9 @@ function processComputed(
|
|
|
347
344
|
setDefaultVariants: (
|
|
348
345
|
newDefaults: VariantValues<Record<string, unknown>>,
|
|
349
346
|
) => {
|
|
350
|
-
// Only apply defaults for variants not
|
|
347
|
+
// Only apply defaults for variants not explicitly set in props
|
|
351
348
|
for (const [key, value] of Object.entries(newDefaults)) {
|
|
352
|
-
if (
|
|
349
|
+
if (propsVariants[key] === undefined) {
|
|
353
350
|
updatedVariants[key] = value;
|
|
354
351
|
}
|
|
355
352
|
}
|
|
@@ -367,46 +364,67 @@ function processComputed(
|
|
|
367
364
|
return { classes, style, updatedVariants };
|
|
368
365
|
}
|
|
369
366
|
|
|
370
|
-
|
|
371
|
-
* Normalizes a key source (array or component) to an object with keys and defaults.
|
|
372
|
-
*/
|
|
373
|
-
function normalizeKeySource(source: unknown): {
|
|
367
|
+
interface NormalizedSource {
|
|
374
368
|
keys: string[];
|
|
369
|
+
variantKeys: string[];
|
|
375
370
|
defaults: Record<string, unknown>;
|
|
376
|
-
|
|
371
|
+
isComponent: boolean;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Normalizes a key source (array or component) to an object with keys, variantKeys, defaults, and isComponent flag.
|
|
376
|
+
*/
|
|
377
|
+
function normalizeKeySource(source: unknown): NormalizedSource {
|
|
377
378
|
if (Array.isArray(source)) {
|
|
378
|
-
return {
|
|
379
|
+
return {
|
|
380
|
+
keys: source as string[],
|
|
381
|
+
variantKeys: source as string[],
|
|
382
|
+
defaults: {},
|
|
383
|
+
isComponent: false,
|
|
384
|
+
};
|
|
379
385
|
}
|
|
380
|
-
// Components are functions with keys
|
|
386
|
+
// Components are functions with keys and variantKeys properties
|
|
381
387
|
if (
|
|
382
388
|
source &&
|
|
383
389
|
(typeof source === "object" || typeof source === "function") &&
|
|
384
|
-
"keys" in source
|
|
390
|
+
"keys" in source &&
|
|
391
|
+
"variantKeys" in source
|
|
385
392
|
) {
|
|
386
393
|
const keys = [...(source as { keys: string[] }).keys] as string[];
|
|
394
|
+
const variantKeys = [
|
|
395
|
+
...(source as { variantKeys: string[] }).variantKeys,
|
|
396
|
+
] as string[];
|
|
387
397
|
const defaults =
|
|
388
398
|
"getVariants" in source
|
|
389
399
|
? (
|
|
390
400
|
source as { getVariants: () => Record<string, unknown> }
|
|
391
401
|
).getVariants()
|
|
392
402
|
: {};
|
|
393
|
-
return { keys, defaults };
|
|
403
|
+
return { keys, variantKeys, defaults, isComponent: true };
|
|
394
404
|
}
|
|
395
|
-
return { keys: [], defaults: {} };
|
|
405
|
+
return { keys: [], variantKeys: [], defaults: {}, isComponent: false };
|
|
396
406
|
}
|
|
397
407
|
|
|
398
408
|
/**
|
|
399
409
|
* Splits props into multiple groups based on key sources.
|
|
410
|
+
* Only the first component claims styling props (class/className/style).
|
|
411
|
+
* Subsequent components only receive variant props.
|
|
412
|
+
* Arrays always receive their listed keys but don't claim styling props.
|
|
400
413
|
*/
|
|
401
414
|
function splitPropsImpl(
|
|
402
415
|
selfKeys: string[],
|
|
416
|
+
selfVariantKeys: string[],
|
|
403
417
|
selfDefaults: Record<string, unknown>,
|
|
418
|
+
selfIsComponent: boolean,
|
|
404
419
|
props: Record<string, unknown>,
|
|
405
|
-
sources:
|
|
420
|
+
sources: NormalizedSource[],
|
|
406
421
|
): Record<string, unknown>[] {
|
|
407
422
|
const allUsedKeys = new Set<string>(selfKeys);
|
|
408
423
|
const results: Record<string, unknown>[] = [];
|
|
409
424
|
|
|
425
|
+
// Track if styling has been claimed by a component
|
|
426
|
+
let stylingClaimed = selfIsComponent;
|
|
427
|
+
|
|
410
428
|
// Self result with defaults
|
|
411
429
|
const selfResult: Record<string, unknown> = {};
|
|
412
430
|
// First apply defaults
|
|
@@ -426,20 +444,33 @@ function splitPropsImpl(
|
|
|
426
444
|
// Process each source
|
|
427
445
|
for (const source of sources) {
|
|
428
446
|
const sourceResult: Record<string, unknown> = {};
|
|
429
|
-
|
|
447
|
+
|
|
448
|
+
// Determine which keys this source should use
|
|
449
|
+
// Components use variantKeys if styling has already been claimed
|
|
450
|
+
// Arrays always use their listed keys
|
|
451
|
+
const effectiveKeys =
|
|
452
|
+
source.isComponent && stylingClaimed ? source.variantKeys : source.keys;
|
|
453
|
+
|
|
454
|
+
// First apply defaults (only for variant keys if component and styling claimed)
|
|
430
455
|
for (const [key, value] of Object.entries(source.defaults)) {
|
|
431
|
-
if (
|
|
456
|
+
if (effectiveKeys.includes(key)) {
|
|
432
457
|
sourceResult[key] = value;
|
|
433
458
|
}
|
|
434
459
|
}
|
|
460
|
+
|
|
435
461
|
// Then override with props
|
|
436
|
-
for (const key of
|
|
462
|
+
for (const key of effectiveKeys) {
|
|
437
463
|
allUsedKeys.add(key);
|
|
438
464
|
if (key in props) {
|
|
439
465
|
sourceResult[key] = props[key];
|
|
440
466
|
}
|
|
441
467
|
}
|
|
442
468
|
results.push(sourceResult);
|
|
469
|
+
|
|
470
|
+
// If this is a component that hasn't claimed styling yet, mark styling as claimed
|
|
471
|
+
if (source.isComponent && !stylingClaimed) {
|
|
472
|
+
stylingClaimed = true;
|
|
473
|
+
}
|
|
443
474
|
}
|
|
444
475
|
|
|
445
476
|
// Rest - keys not used by anyone
|
|
@@ -457,6 +488,9 @@ function splitPropsImpl(
|
|
|
457
488
|
/**
|
|
458
489
|
* Splits props into multiple groups based on key sources.
|
|
459
490
|
* Each source gets its own result object containing all its matching keys.
|
|
491
|
+
* The first component source claims styling props (class/className/style).
|
|
492
|
+
* Subsequent components only receive variant props.
|
|
493
|
+
* Arrays receive their listed keys but don't claim styling props.
|
|
460
494
|
* The last element is always the "rest" containing keys not claimed by any source.
|
|
461
495
|
*
|
|
462
496
|
* @example
|
|
@@ -464,8 +498,10 @@ function splitPropsImpl(
|
|
|
464
498
|
* const [buttonProps, inputProps, rest] = splitProps(
|
|
465
499
|
* props,
|
|
466
500
|
* buttonComponent,
|
|
467
|
-
* inputComponent
|
|
501
|
+
* inputComponent,
|
|
468
502
|
* );
|
|
503
|
+
* // buttonProps has class/style + button variants
|
|
504
|
+
* // inputProps has only input variants (no class/style)
|
|
469
505
|
* ```
|
|
470
506
|
*/
|
|
471
507
|
export const splitProps: SplitPropsFunction = ((
|
|
@@ -477,13 +513,15 @@ export const splitProps: SplitPropsFunction = ((
|
|
|
477
513
|
const normalizedSources = sources.map(normalizeKeySource);
|
|
478
514
|
return splitPropsImpl(
|
|
479
515
|
normalizedSource1.keys,
|
|
516
|
+
normalizedSource1.variantKeys,
|
|
480
517
|
normalizedSource1.defaults,
|
|
518
|
+
normalizedSource1.isComponent,
|
|
481
519
|
props,
|
|
482
520
|
normalizedSources,
|
|
483
521
|
);
|
|
484
522
|
}) as SplitPropsFunction;
|
|
485
523
|
|
|
486
|
-
export function create<M extends Mode>({
|
|
524
|
+
export function create<M extends Mode = "jsx">({
|
|
487
525
|
defaultMode = "jsx" as M,
|
|
488
526
|
transformClass = (className) => className,
|
|
489
527
|
}: CreateParams<M> = {}) {
|
|
@@ -502,9 +540,6 @@ export function create<M extends Mode>({
|
|
|
502
540
|
config as CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
503
541
|
);
|
|
504
542
|
|
|
505
|
-
const getClassPropertyName = (mode: Mode) =>
|
|
506
|
-
mode === "jsx" ? "className" : "class";
|
|
507
|
-
|
|
508
543
|
const getPropsKeys = (mode: Mode) => [
|
|
509
544
|
getClassPropertyName(mode),
|
|
510
545
|
"style",
|
|
@@ -535,6 +570,7 @@ export function create<M extends Mode>({
|
|
|
535
570
|
const computedResult = processComputed(
|
|
536
571
|
config as CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
537
572
|
resolvedVariants,
|
|
573
|
+
variantProps,
|
|
538
574
|
);
|
|
539
575
|
resolvedVariants = computedResult.updatedVariants;
|
|
540
576
|
|
|
@@ -642,17 +678,9 @@ export function create<M extends Mode>({
|
|
|
642
678
|
|
|
643
679
|
component.keys = propsKeys as (keyof MergedVariants | keyof R)[];
|
|
644
680
|
|
|
645
|
-
component.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
): VariantValues<MergedVariants> => {
|
|
649
|
-
return resolveVariants(
|
|
650
|
-
config as CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
651
|
-
variants as VariantValues<Record<string, unknown>>,
|
|
652
|
-
) as VariantValues<MergedVariants>;
|
|
653
|
-
},
|
|
654
|
-
keys: variantKeys as (keyof MergedVariants)[],
|
|
655
|
-
} as OnlyVariantsComponent<MergedVariants>;
|
|
681
|
+
component.variantKeys = variantKeys as (keyof MergedVariants)[];
|
|
682
|
+
|
|
683
|
+
component.propKeys = propsKeys as (keyof MergedVariants | keyof R)[];
|
|
656
684
|
|
|
657
685
|
// Compute base class (without variants) - includes extended base classes
|
|
658
686
|
const extendedBaseClasses: ClassValue[] = [];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CSSProperties, ComponentProps } from "react";
|
|
2
|
+
import { expect, expectTypeOf, test } from "vitest";
|
|
3
|
+
import type { JSXProps } from "./types.ts";
|
|
4
|
+
import { type VariantProps, cv, splitProps } from "./index.ts";
|
|
5
|
+
|
|
6
|
+
test("splitProps", () => {
|
|
7
|
+
const component = cv({ variants: { size: { sm: "sm", md: "md" } } });
|
|
8
|
+
|
|
9
|
+
interface Props
|
|
10
|
+
extends ComponentProps<"div">, VariantProps<typeof component> {}
|
|
11
|
+
const props: Props = { className: "custom", size: "md", id: "my-div" };
|
|
12
|
+
|
|
13
|
+
const [variantProps, rest] = splitProps(props, component);
|
|
14
|
+
expectTypeOf(variantProps.style).toEqualTypeOf<CSSProperties | undefined>();
|
|
15
|
+
expectTypeOf(variantProps.className).toEqualTypeOf<string | undefined>();
|
|
16
|
+
expect(variantProps.className).toBe("custom");
|
|
17
|
+
expect(variantProps).toEqual({ size: "md", className: "custom" });
|
|
18
|
+
expect(
|
|
19
|
+
// @ts-expect-error rest props should not have className
|
|
20
|
+
rest.className,
|
|
21
|
+
).toBeUndefined();
|
|
22
|
+
expect(
|
|
23
|
+
// @ts-expect-error rest props should not have style
|
|
24
|
+
rest.style,
|
|
25
|
+
).toBeUndefined();
|
|
26
|
+
expect(rest).toEqual({ id: "my-div" });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("component props", () => {
|
|
30
|
+
const component = cv({
|
|
31
|
+
style: { fontSize: "16px" },
|
|
32
|
+
variants: { size: { sm: "sm", md: "md" } },
|
|
33
|
+
});
|
|
34
|
+
const props = component({ size: "sm", className: "custom" });
|
|
35
|
+
expectTypeOf(props).toEqualTypeOf<JSXProps>();
|
|
36
|
+
expect(props).toEqual({
|
|
37
|
+
className: "sm custom",
|
|
38
|
+
style: { fontSize: "16px" },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ComponentProps, JSX } from "solid-js";
|
|
2
|
+
import { expect, expectTypeOf, test } from "vitest";
|
|
3
|
+
import { type VariantProps, create, splitProps } from "./index.ts";
|
|
4
|
+
import { type HTMLObjProps } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
const { cv } = create({ defaultMode: "htmlObj" });
|
|
7
|
+
|
|
8
|
+
test("splitProps", () => {
|
|
9
|
+
const component = cv({ variants: { size: { sm: "sm", md: "md" } } });
|
|
10
|
+
|
|
11
|
+
interface Props
|
|
12
|
+
extends ComponentProps<"div">, VariantProps<typeof component> {}
|
|
13
|
+
const props: Props = { class: "custom", size: "md", id: "my-div" };
|
|
14
|
+
|
|
15
|
+
const [variantProps, rest] = splitProps(props, component);
|
|
16
|
+
expectTypeOf(variantProps.style).toEqualTypeOf<
|
|
17
|
+
string | JSX.CSSProperties | undefined
|
|
18
|
+
>();
|
|
19
|
+
expectTypeOf(variantProps.class).toEqualTypeOf<string | undefined>();
|
|
20
|
+
expect(variantProps.class).toBe("custom");
|
|
21
|
+
expect(variantProps).toEqual({ size: "md", class: "custom" });
|
|
22
|
+
expect(rest).toEqual({ id: "my-div" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("component props", () => {
|
|
26
|
+
const component = cv({
|
|
27
|
+
style: { fontSize: "16px" },
|
|
28
|
+
variants: { size: { sm: "sm", md: "md" } },
|
|
29
|
+
});
|
|
30
|
+
const props = component({ size: "sm", className: "custom" });
|
|
31
|
+
expectTypeOf(props).toEqualTypeOf<HTMLObjProps>();
|
|
32
|
+
expect(props).toEqual({
|
|
33
|
+
class: "sm custom",
|
|
34
|
+
style: { "font-size": "16px" },
|
|
35
|
+
});
|
|
36
|
+
});
|