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/src/index.ts
CHANGED
|
@@ -5,14 +5,13 @@ import type {
|
|
|
5
5
|
ClassValue,
|
|
6
6
|
ComponentProps,
|
|
7
7
|
ComponentResult,
|
|
8
|
-
Computed,
|
|
9
|
-
ComputedVariants,
|
|
10
8
|
ExtendableVariants,
|
|
11
9
|
HTMLObjProps,
|
|
12
10
|
HTMLProps,
|
|
13
11
|
JSXProps,
|
|
14
12
|
MergeVariants,
|
|
15
13
|
ModalComponent,
|
|
14
|
+
Refine,
|
|
16
15
|
SplitPropsFunction,
|
|
17
16
|
StyleClassProps,
|
|
18
17
|
StyleClassValue,
|
|
@@ -42,23 +41,23 @@ type ComputeFn = (
|
|
|
42
41
|
skipValues: Record<string, Set<string>> | null,
|
|
43
42
|
classesOut: ClsxClassValue[],
|
|
44
43
|
styleOut: StyleValue,
|
|
45
|
-
runState?:
|
|
44
|
+
runState?: RefineRunState,
|
|
46
45
|
protectedVariants?: Record<string, unknown> | null,
|
|
47
46
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
48
47
|
protectedVariantKeys?: Set<string> | null,
|
|
49
48
|
) => Record<string, unknown>;
|
|
50
49
|
|
|
51
|
-
type
|
|
50
|
+
type ResolveRefineFn = (
|
|
52
51
|
resolved: Record<string, unknown>,
|
|
53
52
|
userVariantProps: Record<string, unknown>,
|
|
54
53
|
filterOwnVariants?: boolean,
|
|
55
|
-
runState?:
|
|
54
|
+
runState?: RefineRunState,
|
|
56
55
|
protectedVariants?: Record<string, unknown> | null,
|
|
57
56
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
58
57
|
protectedVariantKeys?: Set<string> | null,
|
|
59
58
|
) => Record<string, unknown>;
|
|
60
59
|
|
|
61
|
-
interface
|
|
60
|
+
interface RefineRunState {
|
|
62
61
|
remaining: number;
|
|
63
62
|
warned: boolean;
|
|
64
63
|
}
|
|
@@ -67,8 +66,8 @@ interface ComputedRunState {
|
|
|
67
66
|
interface ComponentMeta {
|
|
68
67
|
baseClass: string;
|
|
69
68
|
staticDefaults: Record<string, unknown>;
|
|
70
|
-
// Returns variants set via setDefaultVariants in the
|
|
71
|
-
// null when this component has no resolveDefaults work to do (no `
|
|
69
|
+
// Returns variants set via setDefaultVariants in the refine function chain.
|
|
70
|
+
// null when this component has no resolveDefaults work to do (no `refine`
|
|
72
71
|
// and no extends with work).
|
|
73
72
|
resolveDefaults:
|
|
74
73
|
| ((
|
|
@@ -79,12 +78,18 @@ interface ComponentMeta {
|
|
|
79
78
|
// Returns variant classes + style for this component, used by extending
|
|
80
79
|
// components. Top-level rendering also routes through this.
|
|
81
80
|
compute: ComputeFn;
|
|
82
|
-
|
|
81
|
+
resolveRefine: ResolveRefineFn | null;
|
|
83
82
|
// Reference identity is used to detect mixed-factory `extend`. When a
|
|
84
83
|
// component is extended by a parent from a different `create()` call, the
|
|
85
84
|
// parent applies this transform to the extend's contribution before joining,
|
|
86
85
|
// preserving each factory's transform boundary.
|
|
87
86
|
transformClass: (className: string) => string;
|
|
87
|
+
// Variant keys whose effective definition in this component's chain is a
|
|
88
|
+
// function. An extending component that supplies a non-function variant for
|
|
89
|
+
// the same key uses this to tell us to skip that key (matching the
|
|
90
|
+
// type-level "function variant is replaced by anything in the child" rule).
|
|
91
|
+
// Empty when no key in this chain is a function variant.
|
|
92
|
+
functionVariantKeys: Set<string>;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
const META_KEY = "__meta";
|
|
@@ -94,7 +99,7 @@ const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
|
94
99
|
unknown
|
|
95
100
|
>;
|
|
96
101
|
|
|
97
|
-
const
|
|
102
|
+
const MAX_REFINE_RUNS = 50;
|
|
98
103
|
|
|
99
104
|
function areVariantsEqual(
|
|
100
105
|
a: Record<string, unknown>,
|
|
@@ -111,14 +116,14 @@ function areVariantsEqual(
|
|
|
111
116
|
return true;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
function
|
|
119
|
+
function warnRefineLimit(runState: RefineRunState): void {
|
|
115
120
|
if (runState.warned) return;
|
|
116
121
|
runState.warned = true;
|
|
117
122
|
if (process.env.NODE_ENV !== "production") {
|
|
118
123
|
console.warn(
|
|
119
|
-
"Clava: Maximum
|
|
120
|
-
"
|
|
121
|
-
"
|
|
124
|
+
"Clava: Maximum refine iterations exceeded. This can happen when a " +
|
|
125
|
+
"refine callback calls setVariants or setDefaultVariants, but one " +
|
|
126
|
+
"of the variants changes on every run.",
|
|
122
127
|
);
|
|
123
128
|
}
|
|
124
129
|
}
|
|
@@ -202,16 +207,14 @@ export type Variant<
|
|
|
202
207
|
|
|
203
208
|
export interface CVConfig<
|
|
204
209
|
V extends Variants = {},
|
|
205
|
-
CV extends ComputedVariants = {},
|
|
206
210
|
E extends AnyComponent[] = [],
|
|
207
211
|
> {
|
|
208
212
|
extend?: E;
|
|
209
213
|
class?: ClassValue;
|
|
210
214
|
style?: StyleValue;
|
|
211
215
|
variants?: ExtendableVariants<V, E>;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
computed?: Computed<MergeVariants<V, CV, E>>;
|
|
216
|
+
defaultVariants?: VariantValues<MergeVariants<V, E>>;
|
|
217
|
+
refine?: Refine<MergeVariants<V, E>>;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
interface CreateParams {
|
|
@@ -282,7 +285,7 @@ function extractClassAndStylePrebuilt(value: unknown): PrebuiltValue {
|
|
|
282
285
|
* components.
|
|
283
286
|
*/
|
|
284
287
|
function collectVariantKeys(
|
|
285
|
-
config: CVConfig<Variants,
|
|
288
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
286
289
|
): string[] {
|
|
287
290
|
const keys = new Set<string>();
|
|
288
291
|
|
|
@@ -307,18 +310,11 @@ function collectVariantKeys(
|
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
if (config.computedVariants) {
|
|
311
|
-
for (const key in config.computedVariants) {
|
|
312
|
-
if (!Object.hasOwn(config.computedVariants, key)) continue;
|
|
313
|
-
keys.add(key);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
313
|
return Array.from(keys);
|
|
318
314
|
}
|
|
319
315
|
|
|
320
316
|
function isVariantDisabled(
|
|
321
|
-
config: CVConfig<Variants,
|
|
317
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
322
318
|
key: string,
|
|
323
319
|
): boolean {
|
|
324
320
|
return config.variants?.[key] === null;
|
|
@@ -332,7 +328,7 @@ function getVariantValueKey(value: unknown): string | undefined {
|
|
|
332
328
|
}
|
|
333
329
|
|
|
334
330
|
function isVariantValueDisabled(
|
|
335
|
-
config: CVConfig<Variants,
|
|
331
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
336
332
|
key: string,
|
|
337
333
|
value: unknown,
|
|
338
334
|
): boolean {
|
|
@@ -344,7 +340,7 @@ function isVariantValueDisabled(
|
|
|
344
340
|
}
|
|
345
341
|
|
|
346
342
|
function collectDisabledVariantKeys(
|
|
347
|
-
config: CVConfig<Variants,
|
|
343
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
348
344
|
): Set<string> {
|
|
349
345
|
const keys = new Set<string>();
|
|
350
346
|
if (!config.variants) return keys;
|
|
@@ -358,7 +354,7 @@ function collectDisabledVariantKeys(
|
|
|
358
354
|
}
|
|
359
355
|
|
|
360
356
|
function collectDisabledVariantValues(
|
|
361
|
-
config: CVConfig<Variants,
|
|
357
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
362
358
|
): Record<string, Set<string>> {
|
|
363
359
|
const values: Record<string, Set<string>> = {};
|
|
364
360
|
if (!config.variants) return values;
|
|
@@ -565,14 +561,10 @@ export function create({
|
|
|
565
561
|
}: CreateParams = {}) {
|
|
566
562
|
const cx = (...classes: ClsxClassValue[]) => transformClass(clsx(...classes));
|
|
567
563
|
|
|
568
|
-
const cv = <
|
|
569
|
-
V
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
>(
|
|
573
|
-
config: CVConfig<V, CV, E> = {},
|
|
574
|
-
): CVComponent<V, CV, E> => {
|
|
575
|
-
type MergedVariants = MergeVariants<V, CV, E>;
|
|
564
|
+
const cv = <V extends Variants = {}, const E extends AnyComponent[] = []>(
|
|
565
|
+
config: CVConfig<V, E> = {},
|
|
566
|
+
): CVComponent<V, E> => {
|
|
567
|
+
type MergedVariants = MergeVariants<V, E>;
|
|
576
568
|
|
|
577
569
|
// ----- Pre-computed at creation time -----
|
|
578
570
|
const variantKeys = collectVariantKeys(config);
|
|
@@ -589,41 +581,34 @@ export function create({
|
|
|
589
581
|
const extend = config.extend;
|
|
590
582
|
const hasExtend = !!extend && extend.length > 0;
|
|
591
583
|
const variants = config.variants;
|
|
592
|
-
const
|
|
593
|
-
const computed = config.computed;
|
|
584
|
+
const refine = config.refine;
|
|
594
585
|
const baseStyle = config.style;
|
|
595
586
|
const hasBaseStyle = !!baseStyle;
|
|
596
587
|
|
|
597
|
-
//
|
|
598
|
-
//
|
|
588
|
+
// Split `variants` entries into static entries (object/shorthand) and
|
|
589
|
+
// function-variant entries. Static entries are pre-built into
|
|
590
|
+
// PrebuiltVariant for fast iteration. Function-variant entries override
|
|
591
|
+
// any same-key inherited variant (see `staticExtSkipKeys`).
|
|
599
592
|
const variantEntryNames: string[] = [];
|
|
600
593
|
const variantEntryDefs: PrebuiltVariant[] = [];
|
|
594
|
+
const functionVariantNames: string[] = [];
|
|
595
|
+
const functionVariantFns: Array<(value: unknown) => unknown> = [];
|
|
601
596
|
if (variants) {
|
|
602
597
|
for (const name in variants) {
|
|
603
598
|
if (!Object.hasOwn(variants, name)) continue;
|
|
604
599
|
const variant = (variants as Record<string, unknown>)[name];
|
|
605
600
|
if (variant === null) continue;
|
|
601
|
+
if (typeof variant === "function") {
|
|
602
|
+
functionVariantNames.push(name);
|
|
603
|
+
functionVariantFns.push(variant as (value: unknown) => unknown);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
606
|
variantEntryNames.push(name);
|
|
607
607
|
variantEntryDefs.push(buildPrebuiltVariant(variant));
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
610
|
const variantEntryCount = variantEntryNames.length;
|
|
611
|
-
|
|
612
|
-
// Pre-built computed-variants entries.
|
|
613
|
-
const computedVariantNames: string[] = [];
|
|
614
|
-
const computedVariantFns: Array<(value: unknown) => unknown> = [];
|
|
615
|
-
if (computedVariantsCfg) {
|
|
616
|
-
for (const name in computedVariantsCfg) {
|
|
617
|
-
if (!Object.hasOwn(computedVariantsCfg, name)) continue;
|
|
618
|
-
computedVariantNames.push(name);
|
|
619
|
-
computedVariantFns.push(
|
|
620
|
-
(computedVariantsCfg as Record<string, (value: unknown) => unknown>)[
|
|
621
|
-
name
|
|
622
|
-
] as (value: unknown) => unknown,
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const computedVariantCount = computedVariantNames.length;
|
|
611
|
+
const functionVariantCount = functionVariantNames.length;
|
|
627
612
|
|
|
628
613
|
// Pre-compute static defaults. Includes:
|
|
629
614
|
// - extended components' static defaults
|
|
@@ -702,28 +687,80 @@ export function create({
|
|
|
702
687
|
}
|
|
703
688
|
const extCount = extMetas.length;
|
|
704
689
|
|
|
705
|
-
// Filter to only extends with
|
|
706
|
-
// and `
|
|
690
|
+
// Filter to only extends with refine work in their chain. `resolveDefaults`
|
|
691
|
+
// and `resolveRefine` are populated from the same transitive condition,
|
|
707
692
|
// so one bucket is enough for both resolver paths.
|
|
708
|
-
const
|
|
693
|
+
const extMetasWithRefine: ComponentMeta[] = [];
|
|
709
694
|
for (let i = 0; i < extCount; i++) {
|
|
710
695
|
const meta = extMetas[i];
|
|
711
696
|
if (meta.resolveDefaults) {
|
|
712
|
-
|
|
697
|
+
extMetasWithRefine.push(meta);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const extMetasWithRefineCount = extMetasWithRefine.length;
|
|
701
|
+
const shouldCollectChangedVariants = extMetasWithRefineCount > 0;
|
|
702
|
+
|
|
703
|
+
// Function variant keys inherited from extends, filtered through this
|
|
704
|
+
// component's own variants: a static (object/shorthand) variant in this
|
|
705
|
+
// component replaces an inherited function variant for the same key.
|
|
706
|
+
// The closure is exposed on `ComponentMeta` so any further extending
|
|
707
|
+
// component can detect "ancestor's effective variant for K is a function"
|
|
708
|
+
// and skip it when overriding K with a non-function.
|
|
709
|
+
const functionVariantKeys = new Set<string>();
|
|
710
|
+
for (let i = 0; i < extCount; i++) {
|
|
711
|
+
const fnKeys = extMetas[i].functionVariantKeys;
|
|
712
|
+
for (const k of fnKeys) {
|
|
713
|
+
if (disabledVariantKeys.has(k)) continue;
|
|
714
|
+
functionVariantKeys.add(k);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
718
|
+
functionVariantKeys.add(functionVariantNames[i]);
|
|
719
|
+
}
|
|
720
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
721
|
+
// A static variant in this component replaces an inherited function
|
|
722
|
+
// variant for the same key; from this component onward, the key is no
|
|
723
|
+
// longer a function variant.
|
|
724
|
+
functionVariantKeys.delete(variantEntryNames[i]);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Static-variant keys in this component that override an inherited
|
|
728
|
+
// function variant. Type-level merge says child fully replaces, so the
|
|
729
|
+
// ancestor's function must not run with the child's (object-typed) value.
|
|
730
|
+
let staticVariantsOverridingExtFn: string[] | null = null;
|
|
731
|
+
if (variantEntryCount > 0 && extCount > 0) {
|
|
732
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
733
|
+
const name = variantEntryNames[i];
|
|
734
|
+
for (let j = 0; j < extCount; j++) {
|
|
735
|
+
if (extMetas[j].functionVariantKeys.has(name)) {
|
|
736
|
+
if (!staticVariantsOverridingExtFn) {
|
|
737
|
+
staticVariantsOverridingExtFn = [];
|
|
738
|
+
}
|
|
739
|
+
staticVariantsOverridingExtFn.push(name);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
713
743
|
}
|
|
714
744
|
}
|
|
715
|
-
const extMetasWithComputedCount = extMetasWithComputed.length;
|
|
716
|
-
const shouldCollectChangedVariants = extMetasWithComputedCount > 0;
|
|
717
745
|
|
|
718
746
|
// Pre-compute static skip key/value sets to pass to extends. These never
|
|
719
747
|
// change across calls — when caller passes no skip sets, we reuse the same
|
|
720
748
|
// object and avoid Set allocation.
|
|
721
749
|
let staticExtSkipKeys: Set<string> | null = null;
|
|
722
|
-
if (
|
|
750
|
+
if (
|
|
751
|
+
hasDisabledVariantKeys ||
|
|
752
|
+
functionVariantCount > 0 ||
|
|
753
|
+
staticVariantsOverridingExtFn !== null
|
|
754
|
+
) {
|
|
723
755
|
staticExtSkipKeys = new Set<string>();
|
|
724
756
|
for (const k of disabledVariantKeys) staticExtSkipKeys.add(k);
|
|
725
|
-
for (let i = 0; i <
|
|
726
|
-
staticExtSkipKeys.add(
|
|
757
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
758
|
+
staticExtSkipKeys.add(functionVariantNames[i]);
|
|
759
|
+
}
|
|
760
|
+
if (staticVariantsOverridingExtFn) {
|
|
761
|
+
for (const k of staticVariantsOverridingExtFn) {
|
|
762
|
+
staticExtSkipKeys.add(k);
|
|
763
|
+
}
|
|
727
764
|
}
|
|
728
765
|
}
|
|
729
766
|
// Skip values are passed directly to extends. We can reuse the same object
|
|
@@ -760,12 +797,12 @@ export function create({
|
|
|
760
797
|
|
|
761
798
|
// Pre-create resolveDefaults function — used by parents during their
|
|
762
799
|
// `resolveVariantsHot`. Returns the variants set via setDefaultVariants in
|
|
763
|
-
// the
|
|
800
|
+
// the refine function chain.
|
|
764
801
|
//
|
|
765
|
-
// When this component has no `
|
|
802
|
+
// When this component has no `refine` and no `extend` with work, the
|
|
766
803
|
// function is null — callers can skip iterating it entirely.
|
|
767
804
|
const resolveDefaultsFn: ComponentMeta["resolveDefaults"] =
|
|
768
|
-
|
|
805
|
+
refine || extMetasWithRefineCount > 0
|
|
769
806
|
? (
|
|
770
807
|
childDefaults: Record<string, unknown>,
|
|
771
808
|
userProps: Record<string, unknown> = EMPTY_DEFAULTS,
|
|
@@ -787,21 +824,21 @@ export function create({
|
|
|
787
824
|
resolvedVariants[key] = v;
|
|
788
825
|
}
|
|
789
826
|
|
|
790
|
-
const
|
|
827
|
+
const refineDefaults: Record<string, unknown> = {};
|
|
791
828
|
|
|
792
|
-
for (let i = 0; i <
|
|
793
|
-
const extDefaults =
|
|
829
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
830
|
+
const extDefaults = extMetasWithRefine[i].resolveDefaults!(
|
|
794
831
|
childDefaults,
|
|
795
832
|
userProps,
|
|
796
833
|
);
|
|
797
834
|
for (const k in extDefaults) {
|
|
798
835
|
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
799
|
-
|
|
836
|
+
refineDefaults[k] = extDefaults[k];
|
|
800
837
|
}
|
|
801
838
|
}
|
|
802
839
|
|
|
803
|
-
if (
|
|
804
|
-
// Filter to own variant keys so `
|
|
840
|
+
if (refine) {
|
|
841
|
+
// Filter to own variant keys so `ctx.variants` matches
|
|
805
842
|
// `VariantValues<V>` when this component is used as an extend by
|
|
806
843
|
// a parent that adds extra variant keys (those keys would
|
|
807
844
|
// otherwise leak through `userProps`).
|
|
@@ -812,7 +849,7 @@ export function create({
|
|
|
812
849
|
ownVariants[k] = resolvedVariants[k];
|
|
813
850
|
}
|
|
814
851
|
}
|
|
815
|
-
|
|
852
|
+
refine({
|
|
816
853
|
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
817
854
|
setVariants: noop,
|
|
818
855
|
setDefaultVariants: (newDefaults) => {
|
|
@@ -822,7 +859,7 @@ export function create({
|
|
|
822
859
|
if (userProps[key] !== undefined) continue;
|
|
823
860
|
if (isVariantDisabled(config, key)) continue;
|
|
824
861
|
if (isVariantValueDisabled(config, key, value)) continue;
|
|
825
|
-
|
|
862
|
+
refineDefaults[key] = value;
|
|
826
863
|
}
|
|
827
864
|
},
|
|
828
865
|
addClass: noop,
|
|
@@ -830,12 +867,12 @@ export function create({
|
|
|
830
867
|
});
|
|
831
868
|
}
|
|
832
869
|
|
|
833
|
-
return
|
|
870
|
+
return refineDefaults;
|
|
834
871
|
}
|
|
835
872
|
: null;
|
|
836
873
|
|
|
837
874
|
// Hot path: resolve variants by merging static defaults + extends'
|
|
838
|
-
//
|
|
875
|
+
// refine defaults + user-provided props.
|
|
839
876
|
function resolveVariantsHot(
|
|
840
877
|
propsVariants: Record<string, unknown>,
|
|
841
878
|
): Record<string, unknown> {
|
|
@@ -843,14 +880,14 @@ export function create({
|
|
|
843
880
|
const defaults: Record<string, unknown> = {};
|
|
844
881
|
Object.assign(defaults, staticDefaults);
|
|
845
882
|
|
|
846
|
-
// Apply
|
|
883
|
+
// Apply refine defaults from extended components (only those that have
|
|
847
884
|
// actual work to do).
|
|
848
|
-
for (let i = 0; i <
|
|
849
|
-
const meta =
|
|
850
|
-
const
|
|
851
|
-
for (const k in
|
|
852
|
-
if (!Object.hasOwn(
|
|
853
|
-
defaults[k] =
|
|
885
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
886
|
+
const meta = extMetasWithRefine[i];
|
|
887
|
+
const extDefaults = meta.resolveDefaults!(defaults, propsVariants);
|
|
888
|
+
for (const k in extDefaults) {
|
|
889
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
890
|
+
defaults[k] = extDefaults[k];
|
|
854
891
|
}
|
|
855
892
|
}
|
|
856
893
|
|
|
@@ -872,7 +909,7 @@ export function create({
|
|
|
872
909
|
return result;
|
|
873
910
|
}
|
|
874
911
|
|
|
875
|
-
const
|
|
912
|
+
const runRefineContext = (
|
|
876
913
|
resolved: Record<string, unknown>,
|
|
877
914
|
userVariantProps: Record<string, unknown>,
|
|
878
915
|
filterOwnVariants: boolean,
|
|
@@ -891,12 +928,12 @@ export function create({
|
|
|
891
928
|
let cStyle: StyleValue | null = null;
|
|
892
929
|
let changedVariants: Record<string, unknown> | null = null;
|
|
893
930
|
|
|
894
|
-
if (
|
|
931
|
+
if (refine) {
|
|
895
932
|
let ownVariants = resolved;
|
|
896
933
|
if (filterOwnVariants) {
|
|
897
934
|
// When this component is being extended, `resolved` is the parent's
|
|
898
935
|
// workingResolved (a superset of our variant keys). Filter to our own
|
|
899
|
-
// keys for `ctx.variants` so the user's `
|
|
936
|
+
// keys for `ctx.variants` so the user's `refine` callback sees the
|
|
900
937
|
// shape declared by `VariantValues<V>` and not foreign parent keys.
|
|
901
938
|
const filteredVariants: Record<string, unknown> = {};
|
|
902
939
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
@@ -905,9 +942,9 @@ export function create({
|
|
|
905
942
|
}
|
|
906
943
|
ownVariants = filteredVariants;
|
|
907
944
|
}
|
|
908
|
-
// Lazy-init updatedVariants — many
|
|
909
|
-
// or call setDefaultVariants for keys the user already set,
|
|
910
|
-
// copy is unnecessary in the common case.
|
|
945
|
+
// Lazy-init updatedVariants — many refine callbacks only inspect
|
|
946
|
+
// `variants` or call setDefaultVariants for keys the user already set,
|
|
947
|
+
// so the copy is unnecessary in the common case.
|
|
911
948
|
let updatedVariants: Record<string, unknown> | null = null;
|
|
912
949
|
const localCClasses: ClassValue[] | null = collectOutput ? [] : null;
|
|
913
950
|
let localCStyle: StyleValue | null = null;
|
|
@@ -1003,7 +1040,7 @@ export function create({
|
|
|
1003
1040
|
Object.assign(localCStyle, newStyle);
|
|
1004
1041
|
},
|
|
1005
1042
|
};
|
|
1006
|
-
const result =
|
|
1043
|
+
const result = refine(ctx);
|
|
1007
1044
|
if (collectOutput && result != null) {
|
|
1008
1045
|
const r = extractClassAndStylePrebuilt(result);
|
|
1009
1046
|
if (r.class != null) localCClasses?.push(r.class);
|
|
@@ -1052,14 +1089,14 @@ export function create({
|
|
|
1052
1089
|
pendingProtectedVariants,
|
|
1053
1090
|
protectedVariantKeys,
|
|
1054
1091
|
) => {
|
|
1055
|
-
// Run `
|
|
1092
|
+
// Run `refine` (if any). May modify resolved variants and emit classes
|
|
1056
1093
|
// and styles.
|
|
1057
1094
|
let workingResolved = resolved;
|
|
1058
1095
|
let cClasses: ClassValue[] | null = null;
|
|
1059
1096
|
let cStyle: StyleValue | null = null;
|
|
1060
1097
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1061
|
-
if (
|
|
1062
|
-
const
|
|
1098
|
+
if (refine) {
|
|
1099
|
+
const refineResult = runRefineContext(
|
|
1063
1100
|
resolved,
|
|
1064
1101
|
userVariantProps,
|
|
1065
1102
|
true,
|
|
@@ -1068,20 +1105,20 @@ export function create({
|
|
|
1068
1105
|
pendingProtectedVariants,
|
|
1069
1106
|
protectedVariantKeys,
|
|
1070
1107
|
);
|
|
1071
|
-
workingResolved =
|
|
1072
|
-
cClasses =
|
|
1073
|
-
cStyle =
|
|
1074
|
-
changedVariants =
|
|
1108
|
+
workingResolved = refineResult.workingResolved;
|
|
1109
|
+
cClasses = refineResult.classes;
|
|
1110
|
+
cStyle = refineResult.style;
|
|
1111
|
+
changedVariants = refineResult.changedVariants;
|
|
1075
1112
|
}
|
|
1076
1113
|
|
|
1077
1114
|
// Run extends' contributions first (their full classes + styles) so our
|
|
1078
1115
|
// own base style and variants apply on top, matching the original
|
|
1079
1116
|
// ext1 → ext2 → … → current ordering.
|
|
1080
1117
|
//
|
|
1081
|
-
// Pass explicit user values plus
|
|
1082
|
-
// `userVariantProps`. This lets more-specific
|
|
1118
|
+
// Pass explicit user values plus refine changes as the extends'
|
|
1119
|
+
// `userVariantProps`. This lets more-specific refine decisions stick
|
|
1083
1120
|
// across re-runs while inherited static defaults can still be refined by
|
|
1084
|
-
// the extended component's own
|
|
1121
|
+
// the extended component's own refine chain.
|
|
1085
1122
|
if (hasExtend) {
|
|
1086
1123
|
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
1087
1124
|
// caller-provided sets need merging.
|
|
@@ -1118,7 +1155,7 @@ export function create({
|
|
|
1118
1155
|
}
|
|
1119
1156
|
|
|
1120
1157
|
const extUserVariantProps =
|
|
1121
|
-
|
|
1158
|
+
extMetasWithRefineCount > 0
|
|
1122
1159
|
? getExtUserVariantProps(
|
|
1123
1160
|
userVariantProps,
|
|
1124
1161
|
protectedVariants ?? null,
|
|
@@ -1165,9 +1202,9 @@ export function create({
|
|
|
1165
1202
|
protectedVariantKeys,
|
|
1166
1203
|
);
|
|
1167
1204
|
}
|
|
1168
|
-
// Only sync protected variants when a child
|
|
1205
|
+
// Only sync protected variants when a child refine resolver can
|
|
1169
1206
|
// observe them. Otherwise extUserVariantProps may alias caller props.
|
|
1170
|
-
if (protectedVariants &&
|
|
1207
|
+
if (protectedVariants && extMetasWithRefineCount > 0) {
|
|
1171
1208
|
Object.assign(extUserVariantProps, protectedVariants);
|
|
1172
1209
|
}
|
|
1173
1210
|
}
|
|
@@ -1177,9 +1214,10 @@ export function create({
|
|
|
1177
1214
|
if (hasBaseStyle) Object.assign(styleOut, baseStyle);
|
|
1178
1215
|
|
|
1179
1216
|
// Apply own variants. Skip keys/values come from caller (e.g., parent
|
|
1180
|
-
// wants its own
|
|
1217
|
+
// wants its own function variant to override this variant).
|
|
1181
1218
|
// `variantEntryNames` already excludes disabled keys (those with `null`
|
|
1182
|
-
// value in config), so we don't re-check
|
|
1219
|
+
// value in config) and function variants, so we don't re-check
|
|
1220
|
+
// `disabledVariantKeys` here.
|
|
1183
1221
|
const ownSkipKeys = skipKeys;
|
|
1184
1222
|
const ownSkipValues = skipValues;
|
|
1185
1223
|
for (let i = 0; i < variantEntryCount; i++) {
|
|
@@ -1217,9 +1255,11 @@ export function create({
|
|
|
1217
1255
|
}
|
|
1218
1256
|
}
|
|
1219
1257
|
|
|
1220
|
-
// Apply
|
|
1221
|
-
|
|
1222
|
-
|
|
1258
|
+
// Apply function variants — entries in `variants` whose value is a
|
|
1259
|
+
// function. They run after static variants and override any same-key
|
|
1260
|
+
// inherited variant via `staticExtSkipKeys`.
|
|
1261
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
1262
|
+
const variantName = functionVariantNames[i];
|
|
1223
1263
|
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
1224
1264
|
const selectedValue = workingResolved[variantName];
|
|
1225
1265
|
if (selectedValue === undefined) continue;
|
|
@@ -1231,7 +1271,7 @@ export function create({
|
|
|
1231
1271
|
) {
|
|
1232
1272
|
continue;
|
|
1233
1273
|
}
|
|
1234
|
-
const fn =
|
|
1274
|
+
const fn = functionVariantFns[i];
|
|
1235
1275
|
const computedResult = fn(selectedValue);
|
|
1236
1276
|
if (computedResult == null) continue;
|
|
1237
1277
|
const r = extractClassAndStylePrebuilt(computedResult);
|
|
@@ -1239,7 +1279,8 @@ export function create({
|
|
|
1239
1279
|
if (r.style) Object.assign(styleOut, r.style);
|
|
1240
1280
|
}
|
|
1241
1281
|
|
|
1242
|
-
// Apply `
|
|
1282
|
+
// Apply `refine` results — must come after own variants (static and
|
|
1283
|
+
// function).
|
|
1243
1284
|
if (cClasses) {
|
|
1244
1285
|
for (let i = 0; i < cClasses.length; i++) {
|
|
1245
1286
|
classesOut.push(cClasses[i] as ClsxClassValue);
|
|
@@ -1251,7 +1292,7 @@ export function create({
|
|
|
1251
1292
|
};
|
|
1252
1293
|
|
|
1253
1294
|
const compute: ComputeFn =
|
|
1254
|
-
!
|
|
1295
|
+
!refine && extMetasWithRefineCount === 0
|
|
1255
1296
|
? (
|
|
1256
1297
|
resolved,
|
|
1257
1298
|
userVariantProps,
|
|
@@ -1289,7 +1330,7 @@ export function create({
|
|
|
1289
1330
|
pendingProtectedVariants,
|
|
1290
1331
|
protectedVariantKeys,
|
|
1291
1332
|
) => {
|
|
1292
|
-
runState ??= { remaining:
|
|
1333
|
+
runState ??= { remaining: MAX_REFINE_RUNS, warned: false };
|
|
1293
1334
|
protectedVariants ??= {};
|
|
1294
1335
|
protectedVariantKeys ??= new Set<string>();
|
|
1295
1336
|
let workingResolved = resolved;
|
|
@@ -1359,7 +1400,7 @@ export function create({
|
|
|
1359
1400
|
if (useDirectOutput && runState.remaining === 0) {
|
|
1360
1401
|
// Keep the direct output from the last allowed run. Rolling
|
|
1361
1402
|
// back here would drop it before the fallback copy below.
|
|
1362
|
-
|
|
1403
|
+
warnRefineLimit(runState);
|
|
1363
1404
|
return nextResolved;
|
|
1364
1405
|
}
|
|
1365
1406
|
|
|
@@ -1379,7 +1420,7 @@ export function create({
|
|
|
1379
1420
|
isFirstRun = false;
|
|
1380
1421
|
}
|
|
1381
1422
|
|
|
1382
|
-
|
|
1423
|
+
warnRefineLimit(runState);
|
|
1383
1424
|
|
|
1384
1425
|
for (let i = 0; i < lastClasses.length; i++) {
|
|
1385
1426
|
classesOut.push(lastClasses[i]);
|
|
@@ -1388,7 +1429,7 @@ export function create({
|
|
|
1388
1429
|
return workingResolved;
|
|
1389
1430
|
};
|
|
1390
1431
|
|
|
1391
|
-
const
|
|
1432
|
+
const resolveRefineOnce: ResolveRefineFn = (
|
|
1392
1433
|
resolved,
|
|
1393
1434
|
userVariantProps,
|
|
1394
1435
|
filterOwnVariants = true,
|
|
@@ -1399,8 +1440,8 @@ export function create({
|
|
|
1399
1440
|
) => {
|
|
1400
1441
|
let workingResolved = resolved;
|
|
1401
1442
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1402
|
-
if (
|
|
1403
|
-
const
|
|
1443
|
+
if (refine) {
|
|
1444
|
+
const refineResult = runRefineContext(
|
|
1404
1445
|
resolved,
|
|
1405
1446
|
userVariantProps,
|
|
1406
1447
|
filterOwnVariants,
|
|
@@ -1409,21 +1450,21 @@ export function create({
|
|
|
1409
1450
|
pendingProtectedVariants,
|
|
1410
1451
|
protectedVariantKeys,
|
|
1411
1452
|
);
|
|
1412
|
-
workingResolved =
|
|
1413
|
-
changedVariants =
|
|
1453
|
+
workingResolved = refineResult.workingResolved;
|
|
1454
|
+
changedVariants = refineResult.changedVariants;
|
|
1414
1455
|
}
|
|
1415
1456
|
|
|
1416
|
-
if (
|
|
1457
|
+
if (extMetasWithRefineCount > 0) {
|
|
1417
1458
|
const extUserVariantProps = getExtUserVariantProps(
|
|
1418
1459
|
userVariantProps,
|
|
1419
1460
|
protectedVariants ?? null,
|
|
1420
1461
|
changedVariants,
|
|
1421
1462
|
);
|
|
1422
|
-
for (let i = 0; i <
|
|
1423
|
-
const meta =
|
|
1424
|
-
const
|
|
1425
|
-
if (!
|
|
1426
|
-
workingResolved =
|
|
1463
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1464
|
+
const meta = extMetasWithRefine[i];
|
|
1465
|
+
const resolveRefine = meta.resolveRefine;
|
|
1466
|
+
if (!resolveRefine) continue;
|
|
1467
|
+
workingResolved = resolveRefine(
|
|
1427
1468
|
workingResolved,
|
|
1428
1469
|
extUserVariantProps,
|
|
1429
1470
|
true,
|
|
@@ -1441,8 +1482,8 @@ export function create({
|
|
|
1441
1482
|
return workingResolved;
|
|
1442
1483
|
};
|
|
1443
1484
|
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1485
|
+
const resolveRefine: ResolveRefineFn | null =
|
|
1486
|
+
refine || extMetasWithRefineCount > 0
|
|
1446
1487
|
? (
|
|
1447
1488
|
resolved,
|
|
1448
1489
|
userVariantProps,
|
|
@@ -1452,7 +1493,7 @@ export function create({
|
|
|
1452
1493
|
pendingProtectedVariants,
|
|
1453
1494
|
protectedVariantKeys,
|
|
1454
1495
|
) => {
|
|
1455
|
-
runState ??= { remaining:
|
|
1496
|
+
runState ??= { remaining: MAX_REFINE_RUNS, warned: false };
|
|
1456
1497
|
protectedVariants ??= {};
|
|
1457
1498
|
protectedVariantKeys ??= new Set<string>();
|
|
1458
1499
|
let workingResolved = resolved;
|
|
@@ -1461,7 +1502,7 @@ export function create({
|
|
|
1461
1502
|
while (runState.remaining > 0) {
|
|
1462
1503
|
runState.remaining -= 1;
|
|
1463
1504
|
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1464
|
-
const nextResolved =
|
|
1505
|
+
const nextResolved = resolveRefineOnce(
|
|
1465
1506
|
workingResolved,
|
|
1466
1507
|
userVariantProps,
|
|
1467
1508
|
filterOwnVariants,
|
|
@@ -1499,7 +1540,7 @@ export function create({
|
|
|
1499
1540
|
}
|
|
1500
1541
|
|
|
1501
1542
|
if (reachedLimit) {
|
|
1502
|
-
|
|
1543
|
+
warnRefineLimit(runState);
|
|
1503
1544
|
}
|
|
1504
1545
|
|
|
1505
1546
|
return workingResolved;
|
|
@@ -1521,7 +1562,7 @@ export function create({
|
|
|
1521
1562
|
Object.assign(resolved, staticDefaults);
|
|
1522
1563
|
|
|
1523
1564
|
let userVariantProps: Record<string, unknown>;
|
|
1524
|
-
if (
|
|
1565
|
+
if (extMetasWithRefineCount > 0) {
|
|
1525
1566
|
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1526
1567
|
// object as `userProps`, so we extract one.
|
|
1527
1568
|
const variantProps: Record<string, unknown> = {};
|
|
@@ -1531,12 +1572,12 @@ export function create({
|
|
|
1531
1572
|
variantProps[key] = propsRecord[key];
|
|
1532
1573
|
}
|
|
1533
1574
|
}
|
|
1534
|
-
for (let i = 0; i <
|
|
1535
|
-
const meta =
|
|
1536
|
-
const
|
|
1537
|
-
for (const k in
|
|
1538
|
-
if (!Object.hasOwn(
|
|
1539
|
-
resolved[k] =
|
|
1575
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1576
|
+
const meta = extMetasWithRefine[i];
|
|
1577
|
+
const extDefaults = meta.resolveDefaults!(resolved, variantProps);
|
|
1578
|
+
for (const k in extDefaults) {
|
|
1579
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
1580
|
+
resolved[k] = extDefaults[k];
|
|
1540
1581
|
}
|
|
1541
1582
|
}
|
|
1542
1583
|
for (const k in variantProps) {
|
|
@@ -1610,12 +1651,8 @@ export function create({
|
|
|
1610
1651
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1611
1652
|
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1612
1653
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1613
|
-
if (
|
|
1614
|
-
resolvedVariants =
|
|
1615
|
-
resolvedVariants,
|
|
1616
|
-
variantProps,
|
|
1617
|
-
false,
|
|
1618
|
-
);
|
|
1654
|
+
if (resolveRefine) {
|
|
1655
|
+
resolvedVariants = resolveRefine(resolvedVariants, variantProps, false);
|
|
1619
1656
|
}
|
|
1620
1657
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1621
1658
|
};
|
|
@@ -1642,8 +1679,9 @@ export function create({
|
|
|
1642
1679
|
staticDefaults,
|
|
1643
1680
|
resolveDefaults: resolveDefaultsFn,
|
|
1644
1681
|
compute,
|
|
1645
|
-
|
|
1682
|
+
resolveRefine,
|
|
1646
1683
|
transformClass,
|
|
1684
|
+
functionVariantKeys,
|
|
1647
1685
|
};
|
|
1648
1686
|
|
|
1649
1687
|
const initComponent = <
|
|
@@ -1667,7 +1705,7 @@ export function create({
|
|
|
1667
1705
|
const defaultComponent = ((props: ComponentProps<MergedVariants> = {}) => {
|
|
1668
1706
|
const { className, style } = computeResult(props);
|
|
1669
1707
|
return { class: className, style };
|
|
1670
|
-
}) as CVComponent<V,
|
|
1708
|
+
}) as CVComponent<V, E>;
|
|
1671
1709
|
initComponent(defaultComponent, inputPropsKeys, (props = {}) => {
|
|
1672
1710
|
return computeResult(props).style;
|
|
1673
1711
|
});
|