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/index.ts
CHANGED
|
@@ -42,7 +42,26 @@ type ComputeFn = (
|
|
|
42
42
|
skipValues: Record<string, Set<string>> | null,
|
|
43
43
|
classesOut: ClsxClassValue[],
|
|
44
44
|
styleOut: StyleValue,
|
|
45
|
-
|
|
45
|
+
runState?: ComputedRunState,
|
|
46
|
+
protectedVariants?: Record<string, unknown> | null,
|
|
47
|
+
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
48
|
+
protectedVariantKeys?: Set<string> | null,
|
|
49
|
+
) => Record<string, unknown>;
|
|
50
|
+
|
|
51
|
+
type ResolveComputedFn = (
|
|
52
|
+
resolved: Record<string, unknown>,
|
|
53
|
+
userVariantProps: Record<string, unknown>,
|
|
54
|
+
filterOwnVariants?: boolean,
|
|
55
|
+
runState?: ComputedRunState,
|
|
56
|
+
protectedVariants?: Record<string, unknown> | null,
|
|
57
|
+
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
58
|
+
protectedVariantKeys?: Set<string> | null,
|
|
59
|
+
) => Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
interface ComputedRunState {
|
|
62
|
+
remaining: number;
|
|
63
|
+
warned: boolean;
|
|
64
|
+
}
|
|
46
65
|
|
|
47
66
|
// Internal metadata stored on components but hidden from public types.
|
|
48
67
|
interface ComponentMeta {
|
|
@@ -60,6 +79,7 @@ interface ComponentMeta {
|
|
|
60
79
|
// Returns variant classes + style for this component, used by extending
|
|
61
80
|
// components. Top-level rendering also routes through this.
|
|
62
81
|
compute: ComputeFn;
|
|
82
|
+
resolveComputed: ResolveComputedFn | null;
|
|
63
83
|
// Reference identity is used to detect mixed-factory `extend`. When a
|
|
64
84
|
// component is extended by a parent from a different `create()` call, the
|
|
65
85
|
// parent applies this transform to the extend's contribution before joining,
|
|
@@ -69,14 +89,81 @@ interface ComponentMeta {
|
|
|
69
89
|
|
|
70
90
|
const META_KEY = "__meta";
|
|
71
91
|
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
73
|
-
const hasOwn = Object.prototype.hasOwnProperty;
|
|
74
|
-
|
|
75
92
|
const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
76
93
|
string,
|
|
77
94
|
unknown
|
|
78
95
|
>;
|
|
79
96
|
|
|
97
|
+
const MAX_COMPUTED_RUNS = 50;
|
|
98
|
+
|
|
99
|
+
function areVariantsEqual(
|
|
100
|
+
a: Record<string, unknown>,
|
|
101
|
+
b: Record<string, unknown>,
|
|
102
|
+
): boolean {
|
|
103
|
+
for (const key in a) {
|
|
104
|
+
if (!Object.hasOwn(a, key)) continue;
|
|
105
|
+
if (!Object.is(a[key], b[key])) return false;
|
|
106
|
+
}
|
|
107
|
+
for (const key in b) {
|
|
108
|
+
if (!Object.hasOwn(b, key)) continue;
|
|
109
|
+
if (!Object.hasOwn(a, key)) return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function warnComputedLimit(runState: ComputedRunState): void {
|
|
115
|
+
if (runState.warned) return;
|
|
116
|
+
runState.warned = true;
|
|
117
|
+
if (process.env.NODE_ENV !== "production") {
|
|
118
|
+
console.warn(
|
|
119
|
+
"Clava: Maximum computed update iterations exceeded. This can happen " +
|
|
120
|
+
"when a computed callback calls setVariants or setDefaultVariants, " +
|
|
121
|
+
"but one of the variants changes on every run.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getExtUserVariantProps(
|
|
127
|
+
userVariantProps: Record<string, unknown>,
|
|
128
|
+
protectedVariants: Record<string, unknown> | null,
|
|
129
|
+
changedVariants: Record<string, unknown> | null,
|
|
130
|
+
): Record<string, unknown> {
|
|
131
|
+
const extUserVariantProps: Record<string, unknown> = {};
|
|
132
|
+
Object.assign(extUserVariantProps, userVariantProps);
|
|
133
|
+
if (protectedVariants) {
|
|
134
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
135
|
+
}
|
|
136
|
+
if (changedVariants) {
|
|
137
|
+
Object.assign(extUserVariantProps, changedVariants);
|
|
138
|
+
}
|
|
139
|
+
return extUserVariantProps;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function mergeVariants(
|
|
143
|
+
target: Record<string, unknown>,
|
|
144
|
+
source: Record<string, unknown>,
|
|
145
|
+
skipKeys?: Set<string> | null,
|
|
146
|
+
): boolean {
|
|
147
|
+
let changed = false;
|
|
148
|
+
if (!skipKeys || skipKeys.size === 0) {
|
|
149
|
+
for (const key in source) {
|
|
150
|
+
if (!Object.hasOwn(source, key)) continue;
|
|
151
|
+
const value = source[key];
|
|
152
|
+
if (!Object.is(target[key], value)) changed = true;
|
|
153
|
+
target[key] = value;
|
|
154
|
+
}
|
|
155
|
+
return changed;
|
|
156
|
+
}
|
|
157
|
+
for (const key in source) {
|
|
158
|
+
if (!Object.hasOwn(source, key)) continue;
|
|
159
|
+
if (skipKeys.has(key)) continue;
|
|
160
|
+
const value = source[key];
|
|
161
|
+
if (!Object.is(target[key], value)) changed = true;
|
|
162
|
+
target[key] = value;
|
|
163
|
+
}
|
|
164
|
+
return changed;
|
|
165
|
+
}
|
|
166
|
+
|
|
80
167
|
// Dynamic property access on function requires cast through unknown.
|
|
81
168
|
function getComponentMeta(component: AnyComponent): ComponentMeta | undefined {
|
|
82
169
|
return (component as unknown as Record<string, unknown>)[META_KEY] as
|
|
@@ -210,7 +297,7 @@ function collectVariantKeys(
|
|
|
210
297
|
|
|
211
298
|
if (config.variants) {
|
|
212
299
|
for (const key in config.variants) {
|
|
213
|
-
if (!hasOwn
|
|
300
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
214
301
|
const variant = (config.variants as Record<string, unknown>)[key];
|
|
215
302
|
if (variant === null) {
|
|
216
303
|
keys.delete(key);
|
|
@@ -222,7 +309,7 @@ function collectVariantKeys(
|
|
|
222
309
|
|
|
223
310
|
if (config.computedVariants) {
|
|
224
311
|
for (const key in config.computedVariants) {
|
|
225
|
-
if (!hasOwn
|
|
312
|
+
if (!Object.hasOwn(config.computedVariants, key)) continue;
|
|
226
313
|
keys.add(key);
|
|
227
314
|
}
|
|
228
315
|
}
|
|
@@ -262,7 +349,7 @@ function collectDisabledVariantKeys(
|
|
|
262
349
|
const keys = new Set<string>();
|
|
263
350
|
if (!config.variants) return keys;
|
|
264
351
|
for (const key in config.variants) {
|
|
265
|
-
if (!hasOwn
|
|
352
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
266
353
|
if ((config.variants as Record<string, unknown>)[key] === null) {
|
|
267
354
|
keys.add(key);
|
|
268
355
|
}
|
|
@@ -276,12 +363,12 @@ function collectDisabledVariantValues(
|
|
|
276
363
|
const values: Record<string, Set<string>> = {};
|
|
277
364
|
if (!config.variants) return values;
|
|
278
365
|
for (const key in config.variants) {
|
|
279
|
-
if (!hasOwn
|
|
366
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
280
367
|
const variant = (config.variants as Record<string, unknown>)[key];
|
|
281
368
|
if (!isRecordObject(variant)) continue;
|
|
282
369
|
let bucket: Set<string> | undefined;
|
|
283
370
|
for (const variantValue in variant) {
|
|
284
|
-
if (!hasOwn
|
|
371
|
+
if (!Object.hasOwn(variant, variantValue)) continue;
|
|
285
372
|
if (variant[variantValue] !== null) continue;
|
|
286
373
|
if (!bucket) {
|
|
287
374
|
bucket = new Set<string>();
|
|
@@ -294,13 +381,13 @@ function collectDisabledVariantValues(
|
|
|
294
381
|
}
|
|
295
382
|
|
|
296
383
|
interface NormalizedSource {
|
|
297
|
-
|
|
384
|
+
propKeys: string[];
|
|
298
385
|
variantKeys: string[];
|
|
299
386
|
isComponent: boolean;
|
|
300
387
|
}
|
|
301
388
|
|
|
302
389
|
const EMPTY_SOURCE: NormalizedSource = {
|
|
303
|
-
|
|
390
|
+
propKeys: [],
|
|
304
391
|
variantKeys: [],
|
|
305
392
|
isComponent: false,
|
|
306
393
|
};
|
|
@@ -308,7 +395,7 @@ const EMPTY_SOURCE: NormalizedSource = {
|
|
|
308
395
|
function normalizeKeySource(source: unknown): NormalizedSource {
|
|
309
396
|
if (Array.isArray(source)) {
|
|
310
397
|
return {
|
|
311
|
-
|
|
398
|
+
propKeys: source as string[],
|
|
312
399
|
variantKeys: source as string[],
|
|
313
400
|
isComponent: false,
|
|
314
401
|
};
|
|
@@ -318,17 +405,14 @@ function normalizeKeySource(source: unknown): NormalizedSource {
|
|
|
318
405
|
if (typeof source !== "object" && typeof source !== "function") {
|
|
319
406
|
return EMPTY_SOURCE;
|
|
320
407
|
}
|
|
321
|
-
|
|
322
|
-
if (
|
|
408
|
+
const typed = source as Record<string, unknown>;
|
|
409
|
+
if (typeof typed.getVariants !== "function") return EMPTY_SOURCE;
|
|
410
|
+
if (!Array.isArray(typed.propKeys)) return EMPTY_SOURCE;
|
|
411
|
+
if (!Array.isArray(typed.variantKeys)) return EMPTY_SOURCE;
|
|
323
412
|
|
|
324
|
-
// Component-provided arrays are immutable metadata — reference directly.
|
|
325
|
-
const typed = source as {
|
|
326
|
-
keys: string[];
|
|
327
|
-
variantKeys: string[];
|
|
328
|
-
};
|
|
329
413
|
return {
|
|
330
|
-
|
|
331
|
-
variantKeys: typed.variantKeys,
|
|
414
|
+
propKeys: typed.propKeys as string[],
|
|
415
|
+
variantKeys: typed.variantKeys as string[],
|
|
332
416
|
isComponent: true,
|
|
333
417
|
};
|
|
334
418
|
}
|
|
@@ -368,7 +452,9 @@ function splitPropsImpl(
|
|
|
368
452
|
const sourceResult: Record<string, unknown> = {};
|
|
369
453
|
|
|
370
454
|
const effectiveKeys =
|
|
371
|
-
source.isComponent && stylingClaimed
|
|
455
|
+
source.isComponent && stylingClaimed
|
|
456
|
+
? source.variantKeys
|
|
457
|
+
: source.propKeys;
|
|
372
458
|
|
|
373
459
|
const effectiveKeysLength = effectiveKeys.length;
|
|
374
460
|
for (let i = 0; i < effectiveKeysLength; i++) {
|
|
@@ -422,7 +508,7 @@ export const splitProps: SplitPropsFunction = ((
|
|
|
422
508
|
) => {
|
|
423
509
|
const normalizedSource1 = normalizeKeySource(source1);
|
|
424
510
|
return splitPropsImpl(
|
|
425
|
-
normalizedSource1.
|
|
511
|
+
normalizedSource1.propKeys,
|
|
426
512
|
normalizedSource1.isComponent,
|
|
427
513
|
props,
|
|
428
514
|
sources,
|
|
@@ -455,7 +541,7 @@ function buildPrebuiltVariant(variantDef: unknown): PrebuiltVariant {
|
|
|
455
541
|
const values: Record<string, PrebuiltValue> = {};
|
|
456
542
|
let disabledValues: Set<string> | null = null;
|
|
457
543
|
for (const key in variantDef) {
|
|
458
|
-
if (!hasOwn
|
|
544
|
+
if (!Object.hasOwn(variantDef, key)) continue;
|
|
459
545
|
const value = variantDef[key];
|
|
460
546
|
if (value === null) {
|
|
461
547
|
if (!disabledValues) disabledValues = new Set<string>();
|
|
@@ -514,7 +600,7 @@ export function create({
|
|
|
514
600
|
const variantEntryDefs: PrebuiltVariant[] = [];
|
|
515
601
|
if (variants) {
|
|
516
602
|
for (const name in variants) {
|
|
517
|
-
if (!hasOwn
|
|
603
|
+
if (!Object.hasOwn(variants, name)) continue;
|
|
518
604
|
const variant = (variants as Record<string, unknown>)[name];
|
|
519
605
|
if (variant === null) continue;
|
|
520
606
|
variantEntryNames.push(name);
|
|
@@ -528,7 +614,7 @@ export function create({
|
|
|
528
614
|
const computedVariantFns: Array<(value: unknown) => unknown> = [];
|
|
529
615
|
if (computedVariantsCfg) {
|
|
530
616
|
for (const name in computedVariantsCfg) {
|
|
531
|
-
if (!hasOwn
|
|
617
|
+
if (!Object.hasOwn(computedVariantsCfg, name)) continue;
|
|
532
618
|
computedVariantNames.push(name);
|
|
533
619
|
computedVariantFns.push(
|
|
534
620
|
(computedVariantsCfg as Record<string, (value: unknown) => unknown>)[
|
|
@@ -553,11 +639,11 @@ export function create({
|
|
|
553
639
|
}
|
|
554
640
|
if (variants) {
|
|
555
641
|
for (const name in variants) {
|
|
556
|
-
if (!hasOwn
|
|
642
|
+
if (!Object.hasOwn(variants, name)) continue;
|
|
557
643
|
const variantDef = (variants as Record<string, unknown>)[name];
|
|
558
644
|
if (!isRecordObject(variantDef)) continue;
|
|
559
645
|
if (
|
|
560
|
-
hasOwn
|
|
646
|
+
Object.hasOwn(variantDef, "false") &&
|
|
561
647
|
staticDefaults[name] === undefined
|
|
562
648
|
) {
|
|
563
649
|
staticDefaults[name] = false;
|
|
@@ -570,7 +656,7 @@ export function create({
|
|
|
570
656
|
if (hasAnyDisabled) {
|
|
571
657
|
// Filter disabled variants in-place
|
|
572
658
|
for (const key in staticDefaults) {
|
|
573
|
-
if (!hasOwn
|
|
659
|
+
if (!Object.hasOwn(staticDefaults, key)) continue;
|
|
574
660
|
if (disabledVariantKeys.has(key)) {
|
|
575
661
|
delete staticDefaults[key];
|
|
576
662
|
continue;
|
|
@@ -596,7 +682,7 @@ export function create({
|
|
|
596
682
|
const extBaseClassesArr: string[] = [];
|
|
597
683
|
const extIsolated: boolean[] = [];
|
|
598
684
|
let hasIsolatedExt = false;
|
|
599
|
-
if (
|
|
685
|
+
if (hasExtend) {
|
|
600
686
|
for (const ext of extend) {
|
|
601
687
|
const meta = getComponentMeta(ext);
|
|
602
688
|
if (!meta) continue;
|
|
@@ -616,16 +702,18 @@ export function create({
|
|
|
616
702
|
}
|
|
617
703
|
const extCount = extMetas.length;
|
|
618
704
|
|
|
619
|
-
// Filter to only extends
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
const
|
|
705
|
+
// Filter to only extends with computed work in their chain. `resolveDefaults`
|
|
706
|
+
// and `resolveComputed` are populated from the same transitive condition,
|
|
707
|
+
// so one bucket is enough for both resolver paths.
|
|
708
|
+
const extMetasWithComputed: ComponentMeta[] = [];
|
|
623
709
|
for (let i = 0; i < extCount; i++) {
|
|
624
|
-
|
|
625
|
-
|
|
710
|
+
const meta = extMetas[i];
|
|
711
|
+
if (meta.resolveDefaults) {
|
|
712
|
+
extMetasWithComputed.push(meta);
|
|
626
713
|
}
|
|
627
714
|
}
|
|
628
|
-
const
|
|
715
|
+
const extMetasWithComputedCount = extMetasWithComputed.length;
|
|
716
|
+
const shouldCollectChangedVariants = extMetasWithComputedCount > 0;
|
|
629
717
|
|
|
630
718
|
// Pre-compute static skip key/value sets to pass to extends. These never
|
|
631
719
|
// change across calls — when caller passes no skip sets, we reuse the same
|
|
@@ -652,12 +740,12 @@ export function create({
|
|
|
652
740
|
): void {
|
|
653
741
|
if (!hasAnyDisabled) {
|
|
654
742
|
for (const key in input) {
|
|
655
|
-
if (hasOwn
|
|
743
|
+
if (Object.hasOwn(input, key)) out[key] = input[key];
|
|
656
744
|
}
|
|
657
745
|
return;
|
|
658
746
|
}
|
|
659
747
|
for (const key in input) {
|
|
660
|
-
if (!hasOwn
|
|
748
|
+
if (!Object.hasOwn(input, key)) continue;
|
|
661
749
|
if (disabledVariantKeys.has(key)) continue;
|
|
662
750
|
const value = input[key];
|
|
663
751
|
if (hasDisabledVariantValues) {
|
|
@@ -677,7 +765,7 @@ export function create({
|
|
|
677
765
|
// When this component has no `computed` and no `extend` with work, the
|
|
678
766
|
// function is null — callers can skip iterating it entirely.
|
|
679
767
|
const resolveDefaultsFn: ComponentMeta["resolveDefaults"] =
|
|
680
|
-
computed ||
|
|
768
|
+
computed || extMetasWithComputedCount > 0
|
|
681
769
|
? (
|
|
682
770
|
childDefaults: Record<string, unknown>,
|
|
683
771
|
userProps: Record<string, unknown> = EMPTY_DEFAULTS,
|
|
@@ -687,13 +775,13 @@ export function create({
|
|
|
687
775
|
const resolvedVariants: Record<string, unknown> = {};
|
|
688
776
|
Object.assign(resolvedVariants, staticDefaults);
|
|
689
777
|
for (const key in childDefaults) {
|
|
690
|
-
if (!hasOwn
|
|
778
|
+
if (!Object.hasOwn(childDefaults, key)) continue;
|
|
691
779
|
const v = childDefaults[key];
|
|
692
780
|
if (v === undefined) continue;
|
|
693
781
|
resolvedVariants[key] = v;
|
|
694
782
|
}
|
|
695
783
|
for (const key in userProps) {
|
|
696
|
-
if (!hasOwn
|
|
784
|
+
if (!Object.hasOwn(userProps, key)) continue;
|
|
697
785
|
const v = userProps[key];
|
|
698
786
|
if (v === undefined) continue;
|
|
699
787
|
resolvedVariants[key] = v;
|
|
@@ -701,11 +789,13 @@ export function create({
|
|
|
701
789
|
|
|
702
790
|
const computedDefaults: Record<string, unknown> = {};
|
|
703
791
|
|
|
704
|
-
for (let i = 0; i <
|
|
705
|
-
const extDefaults =
|
|
706
|
-
|
|
792
|
+
for (let i = 0; i < extMetasWithComputedCount; i++) {
|
|
793
|
+
const extDefaults = extMetasWithComputed[i].resolveDefaults!(
|
|
794
|
+
childDefaults,
|
|
795
|
+
userProps,
|
|
796
|
+
);
|
|
707
797
|
for (const k in extDefaults) {
|
|
708
|
-
if (!hasOwn
|
|
798
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
709
799
|
computedDefaults[k] = extDefaults[k];
|
|
710
800
|
}
|
|
711
801
|
}
|
|
@@ -718,7 +808,7 @@ export function create({
|
|
|
718
808
|
const ownVariants: Record<string, unknown> = {};
|
|
719
809
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
720
810
|
const k = variantKeys[i];
|
|
721
|
-
if (hasOwn
|
|
811
|
+
if (Object.hasOwn(resolvedVariants, k)) {
|
|
722
812
|
ownVariants[k] = resolvedVariants[k];
|
|
723
813
|
}
|
|
724
814
|
}
|
|
@@ -727,7 +817,7 @@ export function create({
|
|
|
727
817
|
setVariants: noop,
|
|
728
818
|
setDefaultVariants: (newDefaults) => {
|
|
729
819
|
for (const key in newDefaults) {
|
|
730
|
-
if (!hasOwn
|
|
820
|
+
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
731
821
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
732
822
|
if (userProps[key] !== undefined) continue;
|
|
733
823
|
if (isVariantDisabled(config, key)) continue;
|
|
@@ -755,11 +845,11 @@ export function create({
|
|
|
755
845
|
|
|
756
846
|
// Apply computed defaults from extended components (only those that have
|
|
757
847
|
// actual work to do).
|
|
758
|
-
for (let i = 0; i <
|
|
759
|
-
const meta =
|
|
848
|
+
for (let i = 0; i < extMetasWithComputedCount; i++) {
|
|
849
|
+
const meta = extMetasWithComputed[i];
|
|
760
850
|
const extComputed = meta.resolveDefaults!(defaults, propsVariants);
|
|
761
851
|
for (const k in extComputed) {
|
|
762
|
-
if (!hasOwn
|
|
852
|
+
if (!Object.hasOwn(extComputed, k)) continue;
|
|
763
853
|
defaults[k] = extComputed[k];
|
|
764
854
|
}
|
|
765
855
|
}
|
|
@@ -768,7 +858,7 @@ export function create({
|
|
|
768
858
|
// contractually variant-only here — callers building from a full props
|
|
769
859
|
// object filter to variant keys before calling.
|
|
770
860
|
for (const k in propsVariants) {
|
|
771
|
-
if (!hasOwn
|
|
861
|
+
if (!Object.hasOwn(propsVariants, k)) continue;
|
|
772
862
|
const v = propsVariants[k];
|
|
773
863
|
if (v === undefined) continue;
|
|
774
864
|
defaults[k] = v;
|
|
@@ -782,39 +872,44 @@ export function create({
|
|
|
782
872
|
return result;
|
|
783
873
|
}
|
|
784
874
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
875
|
+
const runComputedContext = (
|
|
876
|
+
resolved: Record<string, unknown>,
|
|
877
|
+
userVariantProps: Record<string, unknown>,
|
|
878
|
+
filterOwnVariants: boolean,
|
|
879
|
+
collectOutput: boolean,
|
|
880
|
+
protectedVariants: Record<string, unknown> | null | undefined,
|
|
881
|
+
pendingProtectedVariants: Record<string, unknown> | null | undefined,
|
|
882
|
+
protectedVariantKeys: Set<string> | null | undefined,
|
|
883
|
+
): {
|
|
884
|
+
workingResolved: Record<string, unknown>;
|
|
885
|
+
changedVariants: Record<string, unknown> | null;
|
|
886
|
+
classes: ClassValue[] | null;
|
|
887
|
+
style: StyleValue | null;
|
|
888
|
+
} => {
|
|
799
889
|
let workingResolved = resolved;
|
|
800
890
|
let cClasses: ClassValue[] | null = null;
|
|
801
891
|
let cStyle: StyleValue | null = null;
|
|
892
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
802
893
|
|
|
803
894
|
if (computed) {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
|
|
895
|
+
let ownVariants = resolved;
|
|
896
|
+
if (filterOwnVariants) {
|
|
897
|
+
// When this component is being extended, `resolved` is the parent's
|
|
898
|
+
// workingResolved (a superset of our variant keys). Filter to our own
|
|
899
|
+
// keys for `ctx.variants` so the user's `computed` callback sees the
|
|
900
|
+
// shape declared by `VariantValues<V>` and not foreign parent keys.
|
|
901
|
+
const filteredVariants: Record<string, unknown> = {};
|
|
902
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
903
|
+
const k = variantKeys[i];
|
|
904
|
+
if (Object.hasOwn(resolved, k)) filteredVariants[k] = resolved[k];
|
|
905
|
+
}
|
|
906
|
+
ownVariants = filteredVariants;
|
|
812
907
|
}
|
|
813
908
|
// Lazy-init updatedVariants — many computeds only inspect `variants`
|
|
814
909
|
// or call setDefaultVariants for keys the user already set, so the
|
|
815
910
|
// copy is unnecessary in the common case.
|
|
816
911
|
let updatedVariants: Record<string, unknown> | null = null;
|
|
817
|
-
const localCClasses: ClassValue[] = [];
|
|
912
|
+
const localCClasses: ClassValue[] | null = collectOutput ? [] : null;
|
|
818
913
|
let localCStyle: StyleValue | null = null;
|
|
819
914
|
const ensureUpdated = (): Record<string, unknown> => {
|
|
820
915
|
if (updatedVariants) return updatedVariants;
|
|
@@ -823,17 +918,40 @@ export function create({
|
|
|
823
918
|
updatedVariants = u;
|
|
824
919
|
return u;
|
|
825
920
|
};
|
|
921
|
+
const setChangedVariant = (
|
|
922
|
+
key: string,
|
|
923
|
+
value: unknown,
|
|
924
|
+
protect = false,
|
|
925
|
+
) => {
|
|
926
|
+
if (shouldCollectChangedVariants) {
|
|
927
|
+
if (!changedVariants) changedVariants = {};
|
|
928
|
+
changedVariants[key] = value;
|
|
929
|
+
}
|
|
930
|
+
if (protect && protectedVariants) {
|
|
931
|
+
protectedVariants[key] = value;
|
|
932
|
+
protectedVariantKeys?.add(key);
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
const getCurrentVariantValue = (key: string) => {
|
|
936
|
+
return updatedVariants ? updatedVariants[key] : ownVariants[key];
|
|
937
|
+
};
|
|
826
938
|
const ctx = {
|
|
827
939
|
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
828
940
|
setVariants: (
|
|
829
941
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
830
942
|
) => {
|
|
831
943
|
if (!hasAnyDisabled) {
|
|
832
|
-
|
|
944
|
+
for (const key in newVariants) {
|
|
945
|
+
if (!Object.hasOwn(newVariants, key)) continue;
|
|
946
|
+
const value = (newVariants as Record<string, unknown>)[key];
|
|
947
|
+
setChangedVariant(key, value, true);
|
|
948
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
949
|
+
ensureUpdated()[key] = value;
|
|
950
|
+
}
|
|
833
951
|
return;
|
|
834
952
|
}
|
|
835
953
|
for (const key in newVariants) {
|
|
836
|
-
if (!hasOwn
|
|
954
|
+
if (!Object.hasOwn(newVariants, key)) continue;
|
|
837
955
|
if (disabledVariantKeys.has(key)) continue;
|
|
838
956
|
const value = (newVariants as Record<string, unknown>)[key];
|
|
839
957
|
if (hasDisabledVariantValues) {
|
|
@@ -845,6 +963,8 @@ export function create({
|
|
|
845
963
|
continue;
|
|
846
964
|
}
|
|
847
965
|
}
|
|
966
|
+
setChangedVariant(key, value, true);
|
|
967
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
848
968
|
ensureUpdated()[key] = value;
|
|
849
969
|
}
|
|
850
970
|
},
|
|
@@ -852,8 +972,9 @@ export function create({
|
|
|
852
972
|
newDefaults: VariantValues<Record<string, unknown>>,
|
|
853
973
|
) => {
|
|
854
974
|
for (const key in newDefaults) {
|
|
855
|
-
if (!hasOwn
|
|
975
|
+
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
856
976
|
if (userVariantProps[key] !== undefined) continue;
|
|
977
|
+
if (protectedVariantKeys?.has(key)) continue;
|
|
857
978
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
858
979
|
if (hasAnyDisabled) {
|
|
859
980
|
if (disabledVariantKeys.has(key)) continue;
|
|
@@ -865,21 +986,27 @@ export function create({
|
|
|
865
986
|
continue;
|
|
866
987
|
}
|
|
867
988
|
}
|
|
989
|
+
setChangedVariant(key, value);
|
|
990
|
+
if (pendingProtectedVariants) {
|
|
991
|
+
pendingProtectedVariants[key] = value;
|
|
992
|
+
}
|
|
993
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
868
994
|
ensureUpdated()[key] = value;
|
|
869
995
|
}
|
|
870
996
|
},
|
|
871
997
|
addClass: (className: ClassValue) => {
|
|
872
|
-
localCClasses
|
|
998
|
+
localCClasses?.push(className);
|
|
873
999
|
},
|
|
874
1000
|
addStyle: (newStyle: StyleValue) => {
|
|
1001
|
+
if (!collectOutput) return;
|
|
875
1002
|
if (!localCStyle) localCStyle = {};
|
|
876
1003
|
Object.assign(localCStyle, newStyle);
|
|
877
1004
|
},
|
|
878
1005
|
};
|
|
879
1006
|
const result = computed(ctx);
|
|
880
|
-
if (result != null) {
|
|
1007
|
+
if (collectOutput && result != null) {
|
|
881
1008
|
const r = extractClassAndStylePrebuilt(result);
|
|
882
|
-
if (r.class != null) localCClasses
|
|
1009
|
+
if (r.class != null) localCClasses?.push(r.class);
|
|
883
1010
|
if (r.style) {
|
|
884
1011
|
if (!localCStyle) localCStyle = {};
|
|
885
1012
|
Object.assign(localCStyle, r.style);
|
|
@@ -888,79 +1015,133 @@ export function create({
|
|
|
888
1015
|
cClasses = localCClasses;
|
|
889
1016
|
cStyle = localCStyle;
|
|
890
1017
|
if (updatedVariants) {
|
|
1018
|
+
const nextResolved: Record<string, unknown> = {};
|
|
1019
|
+
Object.assign(nextResolved, workingResolved);
|
|
891
1020
|
if (hasAnyDisabled) {
|
|
892
1021
|
const filteredUpdated: Record<string, unknown> = {};
|
|
893
1022
|
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
894
|
-
|
|
1023
|
+
Object.assign(nextResolved, filteredUpdated);
|
|
895
1024
|
} else {
|
|
896
|
-
|
|
1025
|
+
Object.assign(nextResolved, updatedVariants);
|
|
897
1026
|
}
|
|
1027
|
+
workingResolved = nextResolved;
|
|
898
1028
|
}
|
|
899
1029
|
}
|
|
900
1030
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
} else {
|
|
909
|
-
extSkipKeys = new Set(skipKeys);
|
|
910
|
-
for (const k of staticExtSkipKeys) extSkipKeys.add(k);
|
|
911
|
-
}
|
|
1031
|
+
return {
|
|
1032
|
+
workingResolved,
|
|
1033
|
+
changedVariants,
|
|
1034
|
+
classes: cClasses,
|
|
1035
|
+
style: cStyle,
|
|
1036
|
+
};
|
|
1037
|
+
};
|
|
912
1038
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1039
|
+
// Core compute path. Called both for top-level rendering (via
|
|
1040
|
+
// `computeResult`) and recursively when this component is used as an
|
|
1041
|
+
// `extend` target by another component. Pushes variant classes (excluding
|
|
1042
|
+
// base class) into `classesOut` and merges styles into `styleOut`.
|
|
1043
|
+
const computeOnce: ComputeFn = (
|
|
1044
|
+
resolved,
|
|
1045
|
+
userVariantProps,
|
|
1046
|
+
skipKeys,
|
|
1047
|
+
skipValues,
|
|
1048
|
+
classesOut,
|
|
1049
|
+
styleOut,
|
|
1050
|
+
runState,
|
|
1051
|
+
protectedVariants,
|
|
1052
|
+
pendingProtectedVariants,
|
|
1053
|
+
protectedVariantKeys,
|
|
1054
|
+
) => {
|
|
1055
|
+
// Run `computed` (if any). May modify resolved variants and emit classes
|
|
1056
|
+
// and styles.
|
|
1057
|
+
let workingResolved = resolved;
|
|
1058
|
+
let cClasses: ClassValue[] | null = null;
|
|
1059
|
+
let cStyle: StyleValue | null = null;
|
|
1060
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
1061
|
+
if (computed) {
|
|
1062
|
+
const computedResult = runComputedContext(
|
|
1063
|
+
resolved,
|
|
1064
|
+
userVariantProps,
|
|
1065
|
+
true,
|
|
1066
|
+
true,
|
|
1067
|
+
protectedVariants,
|
|
1068
|
+
pendingProtectedVariants,
|
|
1069
|
+
protectedVariantKeys,
|
|
1070
|
+
);
|
|
1071
|
+
workingResolved = computedResult.workingResolved;
|
|
1072
|
+
cClasses = computedResult.classes;
|
|
1073
|
+
cStyle = computedResult.style;
|
|
1074
|
+
changedVariants = computedResult.changedVariants;
|
|
933
1075
|
}
|
|
934
1076
|
|
|
935
1077
|
// Run extends' contributions first (their full classes + styles) so our
|
|
936
1078
|
// own base style and variants apply on top, matching the original
|
|
937
1079
|
// ext1 → ext2 → … → current ordering.
|
|
938
1080
|
//
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
//
|
|
942
|
-
// the
|
|
943
|
-
// "user-provided" makes extends' own `setDefaultVariants` skip them, so
|
|
944
|
-
// extends emit variant classes that match what we resolved (rather than
|
|
945
|
-
// re-running their own defaults and emitting a different class).
|
|
946
|
-
// Replacing this with the original `userVariantProps` looks cleaner but
|
|
947
|
-
// breaks "child computed setDefaultVariants overrides parent computed
|
|
948
|
-
// setDefaultVariants" in `tests/computed.test.ts` — extends would then
|
|
949
|
-
// overwrite values the descendant already resolved.
|
|
1081
|
+
// Pass explicit user values plus computed changes as the extends'
|
|
1082
|
+
// `userVariantProps`. This lets more-specific computed decisions stick
|
|
1083
|
+
// across re-runs while inherited static defaults can still be refined by
|
|
1084
|
+
// the extended component's own computed chain.
|
|
950
1085
|
if (hasExtend) {
|
|
1086
|
+
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
1087
|
+
// caller-provided sets need merging.
|
|
1088
|
+
let extSkipKeys: Set<string> | null;
|
|
1089
|
+
if (skipKeys === null) {
|
|
1090
|
+
extSkipKeys = staticExtSkipKeys;
|
|
1091
|
+
} else if (staticExtSkipKeys === null) {
|
|
1092
|
+
extSkipKeys = skipKeys;
|
|
1093
|
+
} else {
|
|
1094
|
+
extSkipKeys = new Set(skipKeys);
|
|
1095
|
+
for (const k of staticExtSkipKeys) extSkipKeys.add(k);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
let extSkipVals: Record<string, Set<string>> | null;
|
|
1099
|
+
if (skipValues === null) {
|
|
1100
|
+
extSkipVals = staticExtSkipValues;
|
|
1101
|
+
} else if (staticExtSkipValues === null) {
|
|
1102
|
+
extSkipVals = skipValues;
|
|
1103
|
+
} else {
|
|
1104
|
+
extSkipVals = {};
|
|
1105
|
+
for (const k in skipValues) {
|
|
1106
|
+
extSkipVals[k] = skipValues[k];
|
|
1107
|
+
}
|
|
1108
|
+
for (const k in staticExtSkipValues) {
|
|
1109
|
+
const existing = extSkipVals[k];
|
|
1110
|
+
if (existing) {
|
|
1111
|
+
const merged = new Set<string>(existing);
|
|
1112
|
+
for (const v of staticExtSkipValues[k]) merged.add(v);
|
|
1113
|
+
extSkipVals[k] = merged;
|
|
1114
|
+
} else {
|
|
1115
|
+
extSkipVals[k] = staticExtSkipValues[k];
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const extUserVariantProps =
|
|
1121
|
+
extMetasWithComputedCount > 0
|
|
1122
|
+
? getExtUserVariantProps(
|
|
1123
|
+
userVariantProps,
|
|
1124
|
+
protectedVariants ?? null,
|
|
1125
|
+
changedVariants,
|
|
1126
|
+
)
|
|
1127
|
+
: userVariantProps;
|
|
951
1128
|
for (let i = 0; i < extCount; i++) {
|
|
952
1129
|
if (hasIsolatedExt && extIsolated[i]) {
|
|
953
1130
|
// Isolated extend (different factory): gather its variant classes
|
|
954
1131
|
// into a scratch array, then push the joined string after applying
|
|
955
1132
|
// its own transformClass. Our outer transform applies on top.
|
|
956
1133
|
const extClasses: ClsxClassValue[] = [];
|
|
957
|
-
extMetas[i].compute(
|
|
958
|
-
workingResolved,
|
|
1134
|
+
workingResolved = extMetas[i].compute(
|
|
959
1135
|
workingResolved,
|
|
1136
|
+
extUserVariantProps,
|
|
960
1137
|
extSkipKeys,
|
|
961
1138
|
extSkipVals,
|
|
962
1139
|
extClasses,
|
|
963
1140
|
styleOut,
|
|
1141
|
+
runState,
|
|
1142
|
+
protectedVariants,
|
|
1143
|
+
pendingProtectedVariants,
|
|
1144
|
+
protectedVariantKeys,
|
|
964
1145
|
);
|
|
965
1146
|
if (extClasses.length > 0) {
|
|
966
1147
|
const joined = clsx(extClasses);
|
|
@@ -971,15 +1152,24 @@ export function create({
|
|
|
971
1152
|
}
|
|
972
1153
|
}
|
|
973
1154
|
} else {
|
|
974
|
-
extMetas[i].compute(
|
|
975
|
-
workingResolved,
|
|
1155
|
+
workingResolved = extMetas[i].compute(
|
|
976
1156
|
workingResolved,
|
|
1157
|
+
extUserVariantProps,
|
|
977
1158
|
extSkipKeys,
|
|
978
1159
|
extSkipVals,
|
|
979
1160
|
classesOut,
|
|
980
1161
|
styleOut,
|
|
1162
|
+
runState,
|
|
1163
|
+
protectedVariants,
|
|
1164
|
+
pendingProtectedVariants,
|
|
1165
|
+
protectedVariantKeys,
|
|
981
1166
|
);
|
|
982
1167
|
}
|
|
1168
|
+
// Only sync protected variants when a child computed resolver can
|
|
1169
|
+
// observe them. Otherwise extUserVariantProps may alias caller props.
|
|
1170
|
+
if (protectedVariants && extMetasWithComputedCount > 0) {
|
|
1171
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
1172
|
+
}
|
|
983
1173
|
}
|
|
984
1174
|
}
|
|
985
1175
|
|
|
@@ -1056,8 +1246,266 @@ export function create({
|
|
|
1056
1246
|
}
|
|
1057
1247
|
}
|
|
1058
1248
|
if (cStyle) Object.assign(styleOut, cStyle);
|
|
1249
|
+
|
|
1250
|
+
return workingResolved;
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
const compute: ComputeFn =
|
|
1254
|
+
!computed && extMetasWithComputedCount === 0
|
|
1255
|
+
? (
|
|
1256
|
+
resolved,
|
|
1257
|
+
userVariantProps,
|
|
1258
|
+
skipKeys,
|
|
1259
|
+
skipValues,
|
|
1260
|
+
classesOut,
|
|
1261
|
+
styleOut,
|
|
1262
|
+
runState,
|
|
1263
|
+
protectedVariants,
|
|
1264
|
+
pendingProtectedVariants,
|
|
1265
|
+
protectedVariantKeys,
|
|
1266
|
+
) => {
|
|
1267
|
+
return computeOnce(
|
|
1268
|
+
resolved,
|
|
1269
|
+
userVariantProps,
|
|
1270
|
+
skipKeys,
|
|
1271
|
+
skipValues,
|
|
1272
|
+
classesOut,
|
|
1273
|
+
styleOut,
|
|
1274
|
+
runState,
|
|
1275
|
+
protectedVariants,
|
|
1276
|
+
pendingProtectedVariants,
|
|
1277
|
+
protectedVariantKeys,
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
: (
|
|
1281
|
+
resolved,
|
|
1282
|
+
userVariantProps,
|
|
1283
|
+
skipKeys,
|
|
1284
|
+
skipValues,
|
|
1285
|
+
classesOut,
|
|
1286
|
+
styleOut,
|
|
1287
|
+
runState,
|
|
1288
|
+
protectedVariants,
|
|
1289
|
+
pendingProtectedVariants,
|
|
1290
|
+
protectedVariantKeys,
|
|
1291
|
+
) => {
|
|
1292
|
+
runState ??= { remaining: MAX_COMPUTED_RUNS, warned: false };
|
|
1293
|
+
protectedVariants ??= {};
|
|
1294
|
+
protectedVariantKeys ??= new Set<string>();
|
|
1295
|
+
let workingResolved = resolved;
|
|
1296
|
+
let lastClasses: ClsxClassValue[] = [];
|
|
1297
|
+
let lastStyle: StyleValue = {};
|
|
1298
|
+
let isFirstRun = true;
|
|
1299
|
+
|
|
1300
|
+
while (runState.remaining > 0) {
|
|
1301
|
+
runState.remaining -= 1;
|
|
1302
|
+
let useDirectOutput = isFirstRun;
|
|
1303
|
+
if (useDirectOutput) {
|
|
1304
|
+
for (const key in styleOut) {
|
|
1305
|
+
if (Object.hasOwn(styleOut, key)) {
|
|
1306
|
+
useDirectOutput = false;
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const classCount = classesOut.length;
|
|
1312
|
+
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1313
|
+
const nextClasses: ClsxClassValue[] = useDirectOutput
|
|
1314
|
+
? classesOut
|
|
1315
|
+
: [];
|
|
1316
|
+
const nextStyle: StyleValue = useDirectOutput ? styleOut : {};
|
|
1317
|
+
const nextResolved = computeOnce(
|
|
1318
|
+
workingResolved,
|
|
1319
|
+
userVariantProps,
|
|
1320
|
+
skipKeys,
|
|
1321
|
+
skipValues,
|
|
1322
|
+
nextClasses,
|
|
1323
|
+
nextStyle,
|
|
1324
|
+
runState,
|
|
1325
|
+
protectedVariants,
|
|
1326
|
+
nextPendingProtectedVariants,
|
|
1327
|
+
protectedVariantKeys,
|
|
1328
|
+
);
|
|
1329
|
+
|
|
1330
|
+
let protectedChanged: boolean;
|
|
1331
|
+
if (pendingProtectedVariants) {
|
|
1332
|
+
protectedChanged = mergeVariants(
|
|
1333
|
+
pendingProtectedVariants,
|
|
1334
|
+
nextPendingProtectedVariants,
|
|
1335
|
+
protectedVariantKeys,
|
|
1336
|
+
);
|
|
1337
|
+
} else {
|
|
1338
|
+
protectedChanged = mergeVariants(
|
|
1339
|
+
protectedVariants,
|
|
1340
|
+
nextPendingProtectedVariants,
|
|
1341
|
+
protectedVariantKeys,
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (
|
|
1346
|
+
!protectedChanged &&
|
|
1347
|
+
(nextResolved === workingResolved ||
|
|
1348
|
+
areVariantsEqual(workingResolved, nextResolved))
|
|
1349
|
+
) {
|
|
1350
|
+
if (!useDirectOutput) {
|
|
1351
|
+
for (let i = 0; i < nextClasses.length; i++) {
|
|
1352
|
+
classesOut.push(nextClasses[i]);
|
|
1353
|
+
}
|
|
1354
|
+
Object.assign(styleOut, nextStyle);
|
|
1355
|
+
}
|
|
1356
|
+
return nextResolved;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (useDirectOutput && runState.remaining === 0) {
|
|
1360
|
+
// Keep the direct output from the last allowed run. Rolling
|
|
1361
|
+
// back here would drop it before the fallback copy below.
|
|
1362
|
+
warnComputedLimit(runState);
|
|
1363
|
+
return nextResolved;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (useDirectOutput) {
|
|
1367
|
+
classesOut.length = classCount;
|
|
1368
|
+
for (const key in styleOut) {
|
|
1369
|
+
if (Object.hasOwn(styleOut, key)) {
|
|
1370
|
+
Reflect.deleteProperty(styleOut, key);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
lastClasses = nextClasses;
|
|
1375
|
+
lastStyle = nextStyle;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
workingResolved = nextResolved;
|
|
1379
|
+
isFirstRun = false;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
warnComputedLimit(runState);
|
|
1383
|
+
|
|
1384
|
+
for (let i = 0; i < lastClasses.length; i++) {
|
|
1385
|
+
classesOut.push(lastClasses[i]);
|
|
1386
|
+
}
|
|
1387
|
+
Object.assign(styleOut, lastStyle);
|
|
1388
|
+
return workingResolved;
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
const resolveComputedOnce: ResolveComputedFn = (
|
|
1392
|
+
resolved,
|
|
1393
|
+
userVariantProps,
|
|
1394
|
+
filterOwnVariants = true,
|
|
1395
|
+
runState,
|
|
1396
|
+
protectedVariants,
|
|
1397
|
+
pendingProtectedVariants,
|
|
1398
|
+
protectedVariantKeys,
|
|
1399
|
+
) => {
|
|
1400
|
+
let workingResolved = resolved;
|
|
1401
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
1402
|
+
if (computed) {
|
|
1403
|
+
const computedResult = runComputedContext(
|
|
1404
|
+
resolved,
|
|
1405
|
+
userVariantProps,
|
|
1406
|
+
filterOwnVariants,
|
|
1407
|
+
false,
|
|
1408
|
+
protectedVariants,
|
|
1409
|
+
pendingProtectedVariants,
|
|
1410
|
+
protectedVariantKeys,
|
|
1411
|
+
);
|
|
1412
|
+
workingResolved = computedResult.workingResolved;
|
|
1413
|
+
changedVariants = computedResult.changedVariants;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (extMetasWithComputedCount > 0) {
|
|
1417
|
+
const extUserVariantProps = getExtUserVariantProps(
|
|
1418
|
+
userVariantProps,
|
|
1419
|
+
protectedVariants ?? null,
|
|
1420
|
+
changedVariants,
|
|
1421
|
+
);
|
|
1422
|
+
for (let i = 0; i < extMetasWithComputedCount; i++) {
|
|
1423
|
+
const meta = extMetasWithComputed[i];
|
|
1424
|
+
const resolveComputed = meta.resolveComputed;
|
|
1425
|
+
if (!resolveComputed) continue;
|
|
1426
|
+
workingResolved = resolveComputed(
|
|
1427
|
+
workingResolved,
|
|
1428
|
+
extUserVariantProps,
|
|
1429
|
+
true,
|
|
1430
|
+
runState,
|
|
1431
|
+
protectedVariants,
|
|
1432
|
+
pendingProtectedVariants,
|
|
1433
|
+
protectedVariantKeys,
|
|
1434
|
+
);
|
|
1435
|
+
if (protectedVariants) {
|
|
1436
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
return workingResolved;
|
|
1059
1442
|
};
|
|
1060
1443
|
|
|
1444
|
+
const resolveComputed: ResolveComputedFn | null =
|
|
1445
|
+
computed || extMetasWithComputedCount > 0
|
|
1446
|
+
? (
|
|
1447
|
+
resolved,
|
|
1448
|
+
userVariantProps,
|
|
1449
|
+
filterOwnVariants = true,
|
|
1450
|
+
runState,
|
|
1451
|
+
protectedVariants,
|
|
1452
|
+
pendingProtectedVariants,
|
|
1453
|
+
protectedVariantKeys,
|
|
1454
|
+
) => {
|
|
1455
|
+
runState ??= { remaining: MAX_COMPUTED_RUNS, warned: false };
|
|
1456
|
+
protectedVariants ??= {};
|
|
1457
|
+
protectedVariantKeys ??= new Set<string>();
|
|
1458
|
+
let workingResolved = resolved;
|
|
1459
|
+
let reachedLimit = true;
|
|
1460
|
+
|
|
1461
|
+
while (runState.remaining > 0) {
|
|
1462
|
+
runState.remaining -= 1;
|
|
1463
|
+
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1464
|
+
const nextResolved = resolveComputedOnce(
|
|
1465
|
+
workingResolved,
|
|
1466
|
+
userVariantProps,
|
|
1467
|
+
filterOwnVariants,
|
|
1468
|
+
runState,
|
|
1469
|
+
protectedVariants,
|
|
1470
|
+
nextPendingProtectedVariants,
|
|
1471
|
+
protectedVariantKeys,
|
|
1472
|
+
);
|
|
1473
|
+
let protectedChanged: boolean;
|
|
1474
|
+
if (pendingProtectedVariants) {
|
|
1475
|
+
protectedChanged = mergeVariants(
|
|
1476
|
+
pendingProtectedVariants,
|
|
1477
|
+
nextPendingProtectedVariants,
|
|
1478
|
+
protectedVariantKeys,
|
|
1479
|
+
);
|
|
1480
|
+
} else {
|
|
1481
|
+
protectedChanged = mergeVariants(
|
|
1482
|
+
protectedVariants,
|
|
1483
|
+
nextPendingProtectedVariants,
|
|
1484
|
+
protectedVariantKeys,
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (
|
|
1489
|
+
!protectedChanged &&
|
|
1490
|
+
(nextResolved === workingResolved ||
|
|
1491
|
+
areVariantsEqual(workingResolved, nextResolved))
|
|
1492
|
+
) {
|
|
1493
|
+
workingResolved = nextResolved;
|
|
1494
|
+
reachedLimit = false;
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
workingResolved = nextResolved;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
if (reachedLimit) {
|
|
1502
|
+
warnComputedLimit(runState);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
return workingResolved;
|
|
1506
|
+
}
|
|
1507
|
+
: null;
|
|
1508
|
+
|
|
1061
1509
|
// Top-level: resolves variants from user props, calls compute, then
|
|
1062
1510
|
// assembles className and style with user-provided class/style overrides.
|
|
1063
1511
|
const computeResult = (
|
|
@@ -1073,26 +1521,26 @@ export function create({
|
|
|
1073
1521
|
Object.assign(resolved, staticDefaults);
|
|
1074
1522
|
|
|
1075
1523
|
let userVariantProps: Record<string, unknown>;
|
|
1076
|
-
if (
|
|
1524
|
+
if (extMetasWithComputedCount > 0) {
|
|
1077
1525
|
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1078
1526
|
// object as `userProps`, so we extract one.
|
|
1079
1527
|
const variantProps: Record<string, unknown> = {};
|
|
1080
1528
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
1081
1529
|
const key = variantKeys[i];
|
|
1082
|
-
if (hasOwn
|
|
1530
|
+
if (Object.hasOwn(propsRecord, key)) {
|
|
1083
1531
|
variantProps[key] = propsRecord[key];
|
|
1084
1532
|
}
|
|
1085
1533
|
}
|
|
1086
|
-
for (let i = 0; i <
|
|
1087
|
-
const meta =
|
|
1534
|
+
for (let i = 0; i < extMetasWithComputedCount; i++) {
|
|
1535
|
+
const meta = extMetasWithComputed[i];
|
|
1088
1536
|
const extComputed = meta.resolveDefaults!(resolved, variantProps);
|
|
1089
1537
|
for (const k in extComputed) {
|
|
1090
|
-
if (!hasOwn
|
|
1538
|
+
if (!Object.hasOwn(extComputed, k)) continue;
|
|
1091
1539
|
resolved[k] = extComputed[k];
|
|
1092
1540
|
}
|
|
1093
1541
|
}
|
|
1094
1542
|
for (const k in variantProps) {
|
|
1095
|
-
if (!hasOwn
|
|
1543
|
+
if (!Object.hasOwn(variantProps, k)) continue;
|
|
1096
1544
|
const v = variantProps[k];
|
|
1097
1545
|
if (v === undefined) continue;
|
|
1098
1546
|
resolved[k] = v;
|
|
@@ -1100,11 +1548,11 @@ export function create({
|
|
|
1100
1548
|
userVariantProps = variantProps;
|
|
1101
1549
|
} else {
|
|
1102
1550
|
// Fast path: walk variantKeys directly against propsRecord. Use
|
|
1103
|
-
// hasOwn so a polluted Object.prototype can't introduce variant
|
|
1551
|
+
// Object.hasOwn so a polluted Object.prototype can't introduce variant
|
|
1104
1552
|
// values the user didn't pass.
|
|
1105
1553
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
1106
1554
|
const key = variantKeys[i];
|
|
1107
|
-
if (!hasOwn
|
|
1555
|
+
if (!Object.hasOwn(propsRecord, key)) continue;
|
|
1108
1556
|
const v = propsRecord[key];
|
|
1109
1557
|
if (v === undefined) continue;
|
|
1110
1558
|
resolved[key] = v;
|
|
@@ -1162,67 +1610,12 @@ export function create({
|
|
|
1162
1610
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1163
1611
|
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1164
1612
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
variants: resolvedVariants as VariantValues<Record<string, unknown>>,
|
|
1172
|
-
setVariants: (
|
|
1173
|
-
newVariants: VariantValues<Record<string, unknown>>,
|
|
1174
|
-
) => {
|
|
1175
|
-
if (!hasAnyDisabled) {
|
|
1176
|
-
Object.assign(updatedVariants, newVariants);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
for (const key in newVariants) {
|
|
1180
|
-
if (!hasOwn.call(newVariants, key)) continue;
|
|
1181
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1182
|
-
const value = (newVariants as Record<string, unknown>)[key];
|
|
1183
|
-
if (hasDisabledVariantValues) {
|
|
1184
|
-
const valueKey = getVariantValueKey(value);
|
|
1185
|
-
if (
|
|
1186
|
-
valueKey != null &&
|
|
1187
|
-
disabledVariantValues[key]?.has(valueKey)
|
|
1188
|
-
) {
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
updatedVariants[key] = value;
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
setDefaultVariants: (
|
|
1196
|
-
newDefaults: VariantValues<Record<string, unknown>>,
|
|
1197
|
-
) => {
|
|
1198
|
-
for (const key in newDefaults) {
|
|
1199
|
-
if (!hasOwn.call(newDefaults, key)) continue;
|
|
1200
|
-
if (variantProps[key] !== undefined) continue;
|
|
1201
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
1202
|
-
if (hasAnyDisabled) {
|
|
1203
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1204
|
-
const valueKey = getVariantValueKey(value);
|
|
1205
|
-
if (
|
|
1206
|
-
valueKey != null &&
|
|
1207
|
-
disabledVariantValues[key]?.has(valueKey)
|
|
1208
|
-
) {
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
updatedVariants[key] = value;
|
|
1213
|
-
}
|
|
1214
|
-
},
|
|
1215
|
-
addClass: noop,
|
|
1216
|
-
addStyle: noop,
|
|
1217
|
-
};
|
|
1218
|
-
computed(ctx);
|
|
1219
|
-
if (hasAnyDisabled) {
|
|
1220
|
-
const filteredUpdated: Record<string, unknown> = {};
|
|
1221
|
-
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
1222
|
-
resolvedVariants = filteredUpdated;
|
|
1223
|
-
} else {
|
|
1224
|
-
resolvedVariants = updatedVariants;
|
|
1225
|
-
}
|
|
1613
|
+
if (resolveComputed) {
|
|
1614
|
+
resolvedVariants = resolveComputed(
|
|
1615
|
+
resolvedVariants,
|
|
1616
|
+
variantProps,
|
|
1617
|
+
false,
|
|
1618
|
+
);
|
|
1226
1619
|
}
|
|
1227
1620
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1228
1621
|
};
|
|
@@ -1233,10 +1626,12 @@ export function create({
|
|
|
1233
1626
|
// `transformClass(clsx(allClasses))` at render time, so applying it here
|
|
1234
1627
|
// would compound (double for own-render, triple+ for extend chains) and
|
|
1235
1628
|
// misbehave for non-idempotent transforms.
|
|
1236
|
-
const computedBaseClass =
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1629
|
+
const computedBaseClass = hasExtend
|
|
1630
|
+
? clsx(
|
|
1631
|
+
...(extBaseClassesArr as ClsxClassValue[]),
|
|
1632
|
+
config.class as ClsxClassValue,
|
|
1633
|
+
)
|
|
1634
|
+
: clsx(config.class as ClsxClassValue);
|
|
1240
1635
|
|
|
1241
1636
|
// Shared closures across the default and modal components.
|
|
1242
1637
|
const classFn = (props: ComponentProps<MergedVariants> = {}) => {
|
|
@@ -1247,6 +1642,7 @@ export function create({
|
|
|
1247
1642
|
staticDefaults,
|
|
1248
1643
|
resolveDefaults: resolveDefaultsFn,
|
|
1249
1644
|
compute,
|
|
1645
|
+
resolveComputed,
|
|
1250
1646
|
transformClass,
|
|
1251
1647
|
};
|
|
1252
1648
|
|
|
@@ -1255,15 +1651,14 @@ export function create({
|
|
|
1255
1651
|
T extends ModalComponent<MergedVariants, R>,
|
|
1256
1652
|
>(
|
|
1257
1653
|
c: T,
|
|
1258
|
-
|
|
1654
|
+
propKeys: string[],
|
|
1259
1655
|
style: T["style"],
|
|
1260
1656
|
): T => {
|
|
1261
1657
|
c.class = classFn;
|
|
1262
1658
|
c.style = style;
|
|
1263
1659
|
c.getVariants = getVariants;
|
|
1264
|
-
c.keys = keys;
|
|
1265
1660
|
c.variantKeys = variantKeys;
|
|
1266
|
-
c.propKeys =
|
|
1661
|
+
c.propKeys = propKeys;
|
|
1267
1662
|
setComponentMeta(c, meta);
|
|
1268
1663
|
return c;
|
|
1269
1664
|
};
|