clava 0.4.1 → 0.5.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 +46 -0
- package/README.md +26 -15
- package/dist/index.d.ts +10 -3
- package/dist/index.js +302 -170
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +415 -338
- package/src/refine-warning.ts +161 -0
- package/src/types.ts +24 -2
- package/tests/build.test.ts +1 -0
- package/tests/component-api.test.ts +81 -55
- package/tests/extend.test.ts +44 -10
- package/tests/prototype-pollution.test.ts +3 -4
- package/tests/refine-warning.test.ts +28 -0
- package/tests/refine.test.ts +300 -181
- package/tests/variants-inference.test.ts +81 -0
package/src/index.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import clsx, { type ClassValue as ClsxClassValue } from "clsx";
|
|
2
|
+
import {
|
|
3
|
+
REFINE_UNSTABLE_TRACKING_WINDOW,
|
|
4
|
+
type RefineRunState,
|
|
5
|
+
type VariantChange,
|
|
6
|
+
accumulateUnstableVariantChanges,
|
|
7
|
+
captureCreationFrame,
|
|
8
|
+
warnRefineLimit,
|
|
9
|
+
} from "./refine-warning.ts";
|
|
2
10
|
import type {
|
|
3
11
|
AnyComponent,
|
|
4
12
|
CVComponent,
|
|
5
13
|
ClassValue,
|
|
6
14
|
ComponentProps,
|
|
7
15
|
ComponentResult,
|
|
16
|
+
DefaultVariants,
|
|
8
17
|
ExtendableVariants,
|
|
9
18
|
HTMLObjProps,
|
|
10
19
|
HTMLProps,
|
|
@@ -45,6 +54,8 @@ type ComputeFn = (
|
|
|
45
54
|
protectedVariants?: Record<string, unknown> | null,
|
|
46
55
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
47
56
|
protectedVariantKeys?: Set<string> | null,
|
|
57
|
+
defaultResolved?: Record<string, unknown>,
|
|
58
|
+
renderOnly?: boolean,
|
|
48
59
|
) => Record<string, unknown>;
|
|
49
60
|
|
|
50
61
|
type ResolveRefineFn = (
|
|
@@ -55,26 +66,18 @@ type ResolveRefineFn = (
|
|
|
55
66
|
protectedVariants?: Record<string, unknown> | null,
|
|
56
67
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
57
68
|
protectedVariantKeys?: Set<string> | null,
|
|
69
|
+
defaultResolved?: Record<string, unknown>,
|
|
58
70
|
) => Record<string, unknown>;
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
72
|
+
type ComputedDefaultVariantFn = (context: {
|
|
73
|
+
defaultValue: unknown;
|
|
74
|
+
variants: Readonly<Record<string, unknown>>;
|
|
75
|
+
}) => unknown;
|
|
64
76
|
|
|
65
77
|
// Internal metadata stored on components but hidden from public types.
|
|
66
78
|
interface ComponentMeta {
|
|
67
79
|
baseClass: string;
|
|
68
80
|
staticDefaults: Record<string, unknown>;
|
|
69
|
-
// Returns variants set via setDefaultVariants in the refine function chain.
|
|
70
|
-
// null when this component has no resolveDefaults work to do (no `refine`
|
|
71
|
-
// and no extends with work).
|
|
72
|
-
resolveDefaults:
|
|
73
|
-
| ((
|
|
74
|
-
childDefaults: Record<string, unknown>,
|
|
75
|
-
userProps?: Record<string, unknown>,
|
|
76
|
-
) => Record<string, unknown>)
|
|
77
|
-
| null;
|
|
78
81
|
// Returns variant classes + style for this component, used by extending
|
|
79
82
|
// components. Top-level rendering also routes through this.
|
|
80
83
|
compute: ComputeFn;
|
|
@@ -90,6 +93,10 @@ interface ComponentMeta {
|
|
|
90
93
|
// type-level "function variant is replaced by anything in the child" rule).
|
|
91
94
|
// Empty when no key in this chain is a function variant.
|
|
92
95
|
functionVariantKeys: Set<string>;
|
|
96
|
+
// Variant keys with computed defaults anywhere in this component's chain.
|
|
97
|
+
// Child components use this to preserve inherited computed defaults through
|
|
98
|
+
// `defaultValue` without preserving their own prior computed result.
|
|
99
|
+
computedDefaultKeys: Set<string>;
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
const META_KEY = "__meta";
|
|
@@ -105,13 +112,6 @@ const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
|
105
112
|
|
|
106
113
|
const MAX_REFINE_RUNS = 50;
|
|
107
114
|
|
|
108
|
-
// Once a refine loop is within this many iterations of the cap, start tracking
|
|
109
|
-
// every variant key that changes between iterations so the warning can report
|
|
110
|
-
// every key that contributed to the oscillation, not just the keys that
|
|
111
|
-
// happened to flip on the final step. Convergent loops (the common case) exit
|
|
112
|
-
// well before this threshold and pay no per-iteration tracking cost.
|
|
113
|
-
const REFINE_UNSTABLE_TRACKING_WINDOW = 10;
|
|
114
|
-
|
|
115
115
|
function areVariantsEqual(
|
|
116
116
|
a: Record<string, unknown>,
|
|
117
117
|
b: Record<string, unknown>,
|
|
@@ -127,96 +127,6 @@ function areVariantsEqual(
|
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
interface CreationFrame {
|
|
131
|
-
stack?: string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Captures the call site of the function passed in `skipFn` so refine-limit
|
|
135
|
-
// warnings can point developers at the originating `cv()` call. Returns
|
|
136
|
-
// `undefined` in production so bundlers that replace `process.env.NODE_ENV` at
|
|
137
|
-
// build time can drop the entire warning machinery. The underlying `.stack`
|
|
138
|
-
// string is formatted lazily on first access in every major engine (V8,
|
|
139
|
-
// SpiderMonkey, JavaScriptCore), so holding the captured frame for the
|
|
140
|
-
// lifetime of the component is cheap when no warning fires.
|
|
141
|
-
function captureCreationFrame(skipFn: Function): CreationFrame | undefined {
|
|
142
|
-
if (process.env.NODE_ENV === "production") return undefined;
|
|
143
|
-
if (typeof Error.captureStackTrace === "function") {
|
|
144
|
-
const holder: CreationFrame = {};
|
|
145
|
-
Error.captureStackTrace(holder, skipFn);
|
|
146
|
-
return holder;
|
|
147
|
-
}
|
|
148
|
-
// Engines without `Error.captureStackTrace` (SpiderMonkey, JavaScriptCore)
|
|
149
|
-
// can't strip internal frames, but their `Error.stack` getter is still
|
|
150
|
-
// lazy, so returning the Error instance defers the format cost. The
|
|
151
|
-
// resulting trace includes 1–2 extra frames at the top from this helper and
|
|
152
|
-
// `cv` itself.
|
|
153
|
-
return new Error();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function formatCreationStack(frame: CreationFrame): string | undefined {
|
|
157
|
-
let stack = frame.stack;
|
|
158
|
-
if (!stack) return undefined;
|
|
159
|
-
// V8 prefixes the stack with a leading "Error" / "Error: message" line that
|
|
160
|
-
// isn't meaningful for a captured location — drop it.
|
|
161
|
-
const newlineIdx = stack.indexOf("\n");
|
|
162
|
-
if (newlineIdx > 0) {
|
|
163
|
-
const firstLine = stack.slice(0, newlineIdx);
|
|
164
|
-
if (firstLine === "Error" || firstLine.startsWith("Error:")) {
|
|
165
|
-
stack = stack.slice(newlineIdx + 1);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return stack;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Accumulates the union of variant keys that differ between `prev` and `next`
|
|
172
|
-
// into `into`. Called on every non-converging iteration of the refine loop so
|
|
173
|
-
// the refine-limit warning can report any key that ever changed across runs,
|
|
174
|
-
// not just the keys that changed on the final iteration (two keys flipping at
|
|
175
|
-
// different cadences could otherwise hide each other on the last step).
|
|
176
|
-
function accumulateUnstableVariantKeys(
|
|
177
|
-
into: Set<string>,
|
|
178
|
-
prev: Record<string, unknown>,
|
|
179
|
-
next: Record<string, unknown>,
|
|
180
|
-
): void {
|
|
181
|
-
for (const key in next) {
|
|
182
|
-
if (!Object.hasOwn(next, key)) continue;
|
|
183
|
-
if (!Object.is(prev[key], next[key])) {
|
|
184
|
-
into.add(key);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
for (const key in prev) {
|
|
188
|
-
if (!Object.hasOwn(prev, key)) continue;
|
|
189
|
-
if (Object.hasOwn(next, key)) continue;
|
|
190
|
-
into.add(key);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function warnRefineLimit(
|
|
195
|
-
runState: RefineRunState,
|
|
196
|
-
creationFrame: CreationFrame | undefined,
|
|
197
|
-
unstableKeys: Set<string> | null,
|
|
198
|
-
): void {
|
|
199
|
-
// Bundlers are expected to replace this branch with a production literal,
|
|
200
|
-
// allowing warning-only code below to be removed from consumer bundles.
|
|
201
|
-
if (process.env.NODE_ENV === "production") return;
|
|
202
|
-
if (runState.warned) return;
|
|
203
|
-
runState.warned = true;
|
|
204
|
-
let message =
|
|
205
|
-
"Clava: Maximum refine iterations exceeded. This can happen when a " +
|
|
206
|
-
"refine callback calls setVariants or setDefaultVariants, but one " +
|
|
207
|
-
"of the variants changes on every run.";
|
|
208
|
-
if (unstableKeys && unstableKeys.size > 0) {
|
|
209
|
-
message += `\nVariant(s) that did not stabilize: ${Array.from(unstableKeys).join(", ")}.`;
|
|
210
|
-
}
|
|
211
|
-
if (creationFrame) {
|
|
212
|
-
const creationStack = formatCreationStack(creationFrame);
|
|
213
|
-
if (creationStack) {
|
|
214
|
-
message += `\nComponent created at:\n${creationStack}`;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
console.warn(message);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
130
|
function getExtUserVariantProps(
|
|
221
131
|
userVariantProps: Record<string, unknown>,
|
|
222
132
|
protectedVariants: Record<string, unknown> | null,
|
|
@@ -262,6 +172,31 @@ function mergeVariants(
|
|
|
262
172
|
return changed;
|
|
263
173
|
}
|
|
264
174
|
|
|
175
|
+
function mergeProtectedIntoBase(
|
|
176
|
+
baseResolved: Record<string, unknown>,
|
|
177
|
+
protectedVariants: Record<string, unknown> | null | undefined,
|
|
178
|
+
): Record<string, unknown> {
|
|
179
|
+
if (!protectedVariants) {
|
|
180
|
+
return baseResolved;
|
|
181
|
+
}
|
|
182
|
+
let hasProtected = false;
|
|
183
|
+
for (const key in protectedVariants) {
|
|
184
|
+
if (!Object.hasOwn(protectedVariants, key)) continue;
|
|
185
|
+
hasProtected = true;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (!hasProtected) {
|
|
189
|
+
return baseResolved;
|
|
190
|
+
}
|
|
191
|
+
const resolved: Record<string, unknown> = {};
|
|
192
|
+
Object.assign(resolved, baseResolved);
|
|
193
|
+
for (const key in protectedVariants) {
|
|
194
|
+
if (!Object.hasOwn(protectedVariants, key)) continue;
|
|
195
|
+
resolved[key] = protectedVariants[key];
|
|
196
|
+
}
|
|
197
|
+
return resolved;
|
|
198
|
+
}
|
|
199
|
+
|
|
265
200
|
// Components carry internal metadata on a non-public property so user-facing
|
|
266
201
|
// component types stay clean.
|
|
267
202
|
function getComponentMeta(component: AnyComponent): ComponentMeta | undefined {
|
|
@@ -305,7 +240,7 @@ export interface CVConfig<
|
|
|
305
240
|
class?: ClassValue;
|
|
306
241
|
style?: StyleValue;
|
|
307
242
|
variants?: ExtendableVariants<V, E>;
|
|
308
|
-
defaultVariants?:
|
|
243
|
+
defaultVariants?: DefaultVariants<MergeVariants<V, E>>;
|
|
309
244
|
refine?: Refine<MergeVariants<V, E>>;
|
|
310
245
|
}
|
|
311
246
|
|
|
@@ -313,6 +248,11 @@ interface CreateParams {
|
|
|
313
248
|
transformClass?: (className: string) => string;
|
|
314
249
|
}
|
|
315
250
|
|
|
251
|
+
interface VariantConfigLike {
|
|
252
|
+
extend?: AnyComponent[];
|
|
253
|
+
variants?: Record<string, unknown>;
|
|
254
|
+
}
|
|
255
|
+
|
|
316
256
|
function isRecordObject(value: unknown): value is Record<string, unknown> {
|
|
317
257
|
if (typeof value !== "object") return false;
|
|
318
258
|
if (value == null) return false;
|
|
@@ -376,9 +316,7 @@ function extractClassAndStylePrebuilt(value: unknown): PrebuiltValue {
|
|
|
376
316
|
* Gets all variant keys from a component's config, including extended
|
|
377
317
|
* components.
|
|
378
318
|
*/
|
|
379
|
-
function collectVariantKeys(
|
|
380
|
-
config: CVConfig<Variants, AnyComponent[]>,
|
|
381
|
-
): string[] {
|
|
319
|
+
function collectVariantKeys(config: VariantConfigLike): string[] {
|
|
382
320
|
const keys = new Set<string>();
|
|
383
321
|
|
|
384
322
|
if (config.extend) {
|
|
@@ -393,7 +331,7 @@ function collectVariantKeys(
|
|
|
393
331
|
if (config.variants) {
|
|
394
332
|
for (const key in config.variants) {
|
|
395
333
|
if (!Object.hasOwn(config.variants, key)) continue;
|
|
396
|
-
const variant =
|
|
334
|
+
const variant = config.variants[key];
|
|
397
335
|
if (variant === null) {
|
|
398
336
|
keys.delete(key);
|
|
399
337
|
continue;
|
|
@@ -405,13 +343,6 @@ function collectVariantKeys(
|
|
|
405
343
|
return Array.from(keys);
|
|
406
344
|
}
|
|
407
345
|
|
|
408
|
-
function isVariantDisabled(
|
|
409
|
-
config: CVConfig<Variants, AnyComponent[]>,
|
|
410
|
-
key: string,
|
|
411
|
-
): boolean {
|
|
412
|
-
return config.variants?.[key] === null;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
346
|
function getVariantValueKey(value: unknown): string | undefined {
|
|
416
347
|
if (typeof value === "string") {
|
|
417
348
|
return value;
|
|
@@ -425,28 +356,14 @@ function getVariantValueKey(value: unknown): string | undefined {
|
|
|
425
356
|
return undefined;
|
|
426
357
|
}
|
|
427
358
|
|
|
428
|
-
function
|
|
429
|
-
config: CVConfig<Variants, AnyComponent[]>,
|
|
430
|
-
key: string,
|
|
431
|
-
value: unknown,
|
|
432
|
-
): boolean {
|
|
433
|
-
const valueKey = getVariantValueKey(value);
|
|
434
|
-
if (valueKey == null) return false;
|
|
435
|
-
const variant = config.variants?.[key];
|
|
436
|
-
if (!isRecordObject(variant)) return false;
|
|
437
|
-
return variant[valueKey] === null;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function collectDisabledVariantKeys(
|
|
441
|
-
config: CVConfig<Variants, AnyComponent[]>,
|
|
442
|
-
): Set<string> {
|
|
359
|
+
function collectDisabledVariantKeys(config: VariantConfigLike): Set<string> {
|
|
443
360
|
const keys = new Set<string>();
|
|
444
361
|
if (!config.variants) {
|
|
445
362
|
return keys;
|
|
446
363
|
}
|
|
447
364
|
for (const key in config.variants) {
|
|
448
365
|
if (!Object.hasOwn(config.variants, key)) continue;
|
|
449
|
-
if (
|
|
366
|
+
if (config.variants[key] === null) {
|
|
450
367
|
keys.add(key);
|
|
451
368
|
}
|
|
452
369
|
}
|
|
@@ -454,7 +371,7 @@ function collectDisabledVariantKeys(
|
|
|
454
371
|
}
|
|
455
372
|
|
|
456
373
|
function collectDisabledVariantValues(
|
|
457
|
-
config:
|
|
374
|
+
config: VariantConfigLike,
|
|
458
375
|
): Record<string, Set<string>> {
|
|
459
376
|
const values: Record<string, Set<string>> = {};
|
|
460
377
|
if (!config.variants) {
|
|
@@ -462,7 +379,7 @@ function collectDisabledVariantValues(
|
|
|
462
379
|
}
|
|
463
380
|
for (const key in config.variants) {
|
|
464
381
|
if (!Object.hasOwn(config.variants, key)) continue;
|
|
465
|
-
const variant =
|
|
382
|
+
const variant = config.variants[key];
|
|
466
383
|
if (!isRecordObject(variant)) continue;
|
|
467
384
|
let bucket: Set<string> | undefined;
|
|
468
385
|
for (const variantValue in variant) {
|
|
@@ -722,10 +639,19 @@ export function create({
|
|
|
722
639
|
const variantEntryCount = variantEntryNames.length;
|
|
723
640
|
const functionVariantCount = functionVariantNames.length;
|
|
724
641
|
|
|
642
|
+
const computedDefaultNames: string[] = [];
|
|
643
|
+
const computedDefaultFns: ComputedDefaultVariantFn[] = [];
|
|
644
|
+
const defaultVariants = config.defaultVariants as
|
|
645
|
+
| Record<string, unknown>
|
|
646
|
+
| undefined;
|
|
647
|
+
|
|
725
648
|
// Pre-compute static defaults. Includes:
|
|
726
649
|
// - extended components' static defaults
|
|
727
650
|
// - implicit boolean defaults (variants with a `false` key default to false)
|
|
728
|
-
// - this config's defaultVariants (overriding the above)
|
|
651
|
+
// - this config's literal defaultVariants (overriding the above)
|
|
652
|
+
//
|
|
653
|
+
// Function entries in defaultVariants are computed defaults. They run in
|
|
654
|
+
// the refine loop so they can react to setVariants updates.
|
|
729
655
|
// Then filtered through disabled-variants.
|
|
730
656
|
const staticDefaults: Record<string, unknown> = {};
|
|
731
657
|
if (extend) {
|
|
@@ -749,9 +675,23 @@ export function create({
|
|
|
749
675
|
}
|
|
750
676
|
}
|
|
751
677
|
}
|
|
752
|
-
if (
|
|
753
|
-
|
|
678
|
+
if (defaultVariants) {
|
|
679
|
+
for (const name in defaultVariants) {
|
|
680
|
+
if (!Object.hasOwn(defaultVariants, name)) continue;
|
|
681
|
+
const value = defaultVariants[name];
|
|
682
|
+
if (typeof value === "function") {
|
|
683
|
+
computedDefaultNames.push(name);
|
|
684
|
+
computedDefaultFns.push(value as ComputedDefaultVariantFn);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (value === undefined) {
|
|
688
|
+
Reflect.deleteProperty(staticDefaults, name);
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
staticDefaults[name] = value;
|
|
692
|
+
}
|
|
754
693
|
}
|
|
694
|
+
const computedDefaultCount = computedDefaultNames.length;
|
|
755
695
|
if (hasAnyDisabled) {
|
|
756
696
|
// Filter disabled variants in-place
|
|
757
697
|
for (const key in staticDefaults) {
|
|
@@ -801,13 +741,21 @@ export function create({
|
|
|
801
741
|
}
|
|
802
742
|
const extCount = extMetas.length;
|
|
803
743
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
744
|
+
const inheritedComputedDefaultKeys = new Set<string>();
|
|
745
|
+
for (let i = 0; i < extCount; i++) {
|
|
746
|
+
const keys = extMetas[i].computedDefaultKeys;
|
|
747
|
+
for (const key of keys) {
|
|
748
|
+
inheritedComputedDefaultKeys.add(key);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Filter to only extends with computed default or refine work in their
|
|
753
|
+
// chain. Those are the components that can change resolved variants across
|
|
754
|
+
// fixed-point iterations.
|
|
807
755
|
const extMetasWithRefine: ComponentMeta[] = [];
|
|
808
756
|
for (let i = 0; i < extCount; i++) {
|
|
809
757
|
const meta = extMetas[i];
|
|
810
|
-
if (meta.
|
|
758
|
+
if (meta.resolveRefine) {
|
|
811
759
|
extMetasWithRefine.push(meta);
|
|
812
760
|
}
|
|
813
761
|
}
|
|
@@ -821,7 +769,8 @@ export function create({
|
|
|
821
769
|
// frame is captured at creation time but the underlying `.stack` string is
|
|
822
770
|
// formatted lazily on first access, so component creation stays cheap
|
|
823
771
|
// unless the warning actually fires.
|
|
824
|
-
const canTriggerRefineWarning =
|
|
772
|
+
const canTriggerRefineWarning =
|
|
773
|
+
!!refine || computedDefaultCount > 0 || extMetasWithRefineCount > 0;
|
|
825
774
|
const creationFrame = canTriggerRefineWarning
|
|
826
775
|
? captureCreationFrame(cv)
|
|
827
776
|
: undefined;
|
|
@@ -850,6 +799,11 @@ export function create({
|
|
|
850
799
|
functionVariantKeys.delete(variantEntryNames[i]);
|
|
851
800
|
}
|
|
852
801
|
|
|
802
|
+
const computedDefaultKeys = new Set(inheritedComputedDefaultKeys);
|
|
803
|
+
for (let i = 0; i < computedDefaultCount; i++) {
|
|
804
|
+
computedDefaultKeys.add(computedDefaultNames[i]);
|
|
805
|
+
}
|
|
806
|
+
|
|
853
807
|
// Static-variant keys in this component that override an inherited
|
|
854
808
|
// function variant. Type-level merge says child fully replaces, so the
|
|
855
809
|
// ancestor's function must not run with the child's (object-typed) value.
|
|
@@ -925,84 +879,62 @@ export function create({
|
|
|
925
879
|
}
|
|
926
880
|
}
|
|
927
881
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
// userProps is contractually variant-only (callers pre-filter
|
|
941
|
-
// when starting from a full props object).
|
|
942
|
-
const resolvedVariants: Record<string, unknown> = {};
|
|
943
|
-
Object.assign(resolvedVariants, staticDefaults);
|
|
944
|
-
for (const key in childDefaults) {
|
|
945
|
-
if (!Object.hasOwn(childDefaults, key)) continue;
|
|
946
|
-
const v = childDefaults[key];
|
|
947
|
-
if (v === undefined) continue;
|
|
948
|
-
resolvedVariants[key] = v;
|
|
949
|
-
}
|
|
950
|
-
for (const key in userProps) {
|
|
951
|
-
if (!Object.hasOwn(userProps, key)) continue;
|
|
952
|
-
const v = userProps[key];
|
|
953
|
-
if (v === undefined) continue;
|
|
954
|
-
resolvedVariants[key] = v;
|
|
955
|
-
}
|
|
882
|
+
const isOwnDisabledValue = (key: string, value: unknown): boolean => {
|
|
883
|
+
if (disabledVariantKeys.has(key)) {
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
if (hasDisabledVariantValues) {
|
|
887
|
+
const valueKey = getVariantValueKey(value);
|
|
888
|
+
if (valueKey != null && disabledVariantValues[key]?.has(valueKey)) {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
};
|
|
956
894
|
|
|
957
|
-
|
|
895
|
+
const filterOwnDisabledVariants = (
|
|
896
|
+
input: Record<string, unknown>,
|
|
897
|
+
fallback: Record<string, unknown>,
|
|
898
|
+
): Record<string, unknown> => {
|
|
899
|
+
if (!hasAnyDisabled) {
|
|
900
|
+
return input;
|
|
901
|
+
}
|
|
958
902
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
903
|
+
let hasOwnDisabledValue = false;
|
|
904
|
+
for (const key in input) {
|
|
905
|
+
if (!Object.hasOwn(input, key)) continue;
|
|
906
|
+
const value = input[key];
|
|
907
|
+
if (isOwnDisabledValue(key, value)) {
|
|
908
|
+
hasOwnDisabledValue = true;
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (!hasOwnDisabledValue) {
|
|
913
|
+
return input;
|
|
914
|
+
}
|
|
969
915
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
for (const key in newDefaults) {
|
|
987
|
-
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
988
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
989
|
-
if (userProps[key] !== undefined) continue;
|
|
990
|
-
if (isVariantDisabled(config, key)) continue;
|
|
991
|
-
if (isVariantValueDisabled(config, key, value)) continue;
|
|
992
|
-
refineDefaults[key] = value;
|
|
993
|
-
}
|
|
994
|
-
},
|
|
995
|
-
addClass: noop,
|
|
996
|
-
addStyle: noop,
|
|
997
|
-
});
|
|
998
|
-
}
|
|
916
|
+
const filtered: Record<string, unknown> = {};
|
|
917
|
+
for (const key in input) {
|
|
918
|
+
if (!Object.hasOwn(input, key)) continue;
|
|
919
|
+
const value = input[key];
|
|
920
|
+
if (!isOwnDisabledValue(key, value)) {
|
|
921
|
+
filtered[key] = value;
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const fallbackValue = fallback[key];
|
|
925
|
+
if (
|
|
926
|
+
fallbackValue !== undefined &&
|
|
927
|
+
!isOwnDisabledValue(key, fallbackValue)
|
|
928
|
+
) {
|
|
929
|
+
filtered[key] = fallbackValue;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
999
932
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
: null;
|
|
933
|
+
return filtered;
|
|
934
|
+
};
|
|
1003
935
|
|
|
1004
936
|
// Hot path: resolve variants by merging static defaults + extends'
|
|
1005
|
-
//
|
|
937
|
+
// static defaults + user-provided props.
|
|
1006
938
|
function resolveVariantsHot(
|
|
1007
939
|
propsVariants: Record<string, unknown>,
|
|
1008
940
|
): Record<string, unknown> {
|
|
@@ -1010,17 +942,6 @@ export function create({
|
|
|
1010
942
|
const defaults: Record<string, unknown> = {};
|
|
1011
943
|
Object.assign(defaults, staticDefaults);
|
|
1012
944
|
|
|
1013
|
-
// Apply refine defaults from extended components (only those that have
|
|
1014
|
-
// actual work to do).
|
|
1015
|
-
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1016
|
-
const meta = extMetasWithRefine[i];
|
|
1017
|
-
const extDefaults = meta.resolveDefaults!(defaults, propsVariants);
|
|
1018
|
-
for (const k in extDefaults) {
|
|
1019
|
-
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
1020
|
-
defaults[k] = extDefaults[k];
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
945
|
// Apply propsVariants on top (filter undefined). propsVariants is
|
|
1025
946
|
// contractually variant-only here — callers building from a full props
|
|
1026
947
|
// object filter to variant keys before calling.
|
|
@@ -1041,11 +962,100 @@ export function create({
|
|
|
1041
962
|
return result;
|
|
1042
963
|
}
|
|
1043
964
|
|
|
965
|
+
const runComputedDefaults = (
|
|
966
|
+
resolved: Record<string, unknown>,
|
|
967
|
+
defaultResolved: Record<string, unknown>,
|
|
968
|
+
userVariantProps: Record<string, unknown>,
|
|
969
|
+
filterOwnVariants: boolean,
|
|
970
|
+
protectedVariantKeys: Set<string> | null | undefined,
|
|
971
|
+
): {
|
|
972
|
+
workingResolved: Record<string, unknown>;
|
|
973
|
+
changedVariants: Record<string, unknown> | null;
|
|
974
|
+
} => {
|
|
975
|
+
if (computedDefaultCount === 0) {
|
|
976
|
+
return { workingResolved: resolved, changedVariants: null };
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
let ownVariants = filterOwnVariants ? null : resolved;
|
|
980
|
+
const getOwnVariants = (): Record<string, unknown> => {
|
|
981
|
+
if (ownVariants) {
|
|
982
|
+
return ownVariants;
|
|
983
|
+
}
|
|
984
|
+
const filteredVariants: Record<string, unknown> = {};
|
|
985
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
986
|
+
const key = variantKeys[i];
|
|
987
|
+
if (Object.hasOwn(resolved, key)) {
|
|
988
|
+
filteredVariants[key] = resolved[key];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
ownVariants = filteredVariants;
|
|
992
|
+
return filteredVariants;
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
let updatedVariants: Record<string, unknown> | null = null;
|
|
996
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
997
|
+
const ensureUpdated = (): Record<string, unknown> => {
|
|
998
|
+
if (updatedVariants) {
|
|
999
|
+
return updatedVariants;
|
|
1000
|
+
}
|
|
1001
|
+
const updated: Record<string, unknown> = {};
|
|
1002
|
+
Object.assign(updated, resolved);
|
|
1003
|
+
updatedVariants = updated;
|
|
1004
|
+
return updated;
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
for (let i = 0; i < computedDefaultCount; i++) {
|
|
1008
|
+
const key = computedDefaultNames[i];
|
|
1009
|
+
if (Object.hasOwn(userVariantProps, key)) {
|
|
1010
|
+
if (userVariantProps[key] !== undefined) continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (protectedVariantKeys?.has(key)) continue;
|
|
1013
|
+
|
|
1014
|
+
const variantSnapshot = getOwnVariants();
|
|
1015
|
+
const defaultValue = inheritedComputedDefaultKeys.has(key)
|
|
1016
|
+
? variantSnapshot[key]
|
|
1017
|
+
: defaultResolved[key];
|
|
1018
|
+
const value = computedDefaultFns[i]({
|
|
1019
|
+
defaultValue,
|
|
1020
|
+
variants: variantSnapshot,
|
|
1021
|
+
});
|
|
1022
|
+
if (hasAnyDisabled) {
|
|
1023
|
+
if (disabledVariantKeys.has(key)) continue;
|
|
1024
|
+
const valueKey = getVariantValueKey(value);
|
|
1025
|
+
if (valueKey != null && disabledVariantValues[key]?.has(valueKey)) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (value === undefined) {
|
|
1031
|
+
if (!Object.hasOwn(variantSnapshot, key)) continue;
|
|
1032
|
+
if (shouldCollectChangedVariants) {
|
|
1033
|
+
changedVariants ??= {};
|
|
1034
|
+
changedVariants[key] = value;
|
|
1035
|
+
}
|
|
1036
|
+
Reflect.deleteProperty(ensureUpdated(), key);
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
if (Object.is(variantSnapshot[key], value)) continue;
|
|
1040
|
+
if (shouldCollectChangedVariants) {
|
|
1041
|
+
changedVariants ??= {};
|
|
1042
|
+
changedVariants[key] = value;
|
|
1043
|
+
}
|
|
1044
|
+
ensureUpdated()[key] = value;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return {
|
|
1048
|
+
workingResolved: updatedVariants ?? resolved,
|
|
1049
|
+
changedVariants,
|
|
1050
|
+
};
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1044
1053
|
const runRefineContext = (
|
|
1045
1054
|
resolved: Record<string, unknown>,
|
|
1046
1055
|
userVariantProps: Record<string, unknown>,
|
|
1047
1056
|
filterOwnVariants: boolean,
|
|
1048
1057
|
collectOutput: boolean,
|
|
1058
|
+
applyVariantUpdates: boolean,
|
|
1049
1059
|
protectedVariants: Record<string, unknown> | null | undefined,
|
|
1050
1060
|
pendingProtectedVariants: Record<string, unknown> | null | undefined,
|
|
1051
1061
|
protectedVariantKeys: Set<string> | null | undefined,
|
|
@@ -1077,8 +1087,7 @@ export function create({
|
|
|
1077
1087
|
ownVariants = filteredVariants;
|
|
1078
1088
|
}
|
|
1079
1089
|
// Lazy-init updatedVariants — many refine callbacks only inspect
|
|
1080
|
-
// `variants
|
|
1081
|
-
// so the copy is unnecessary in the common case.
|
|
1090
|
+
// `variants`, so the copy is unnecessary in the common case.
|
|
1082
1091
|
let updatedVariants: Record<string, unknown> | null = null;
|
|
1083
1092
|
const localCClasses: ClassValue[] | null = collectOutput ? [] : null;
|
|
1084
1093
|
let localCStyle: StyleValue | null = null;
|
|
@@ -1115,12 +1124,15 @@ export function create({
|
|
|
1115
1124
|
setVariants: (
|
|
1116
1125
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
1117
1126
|
) => {
|
|
1127
|
+
if (!applyVariantUpdates) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1118
1130
|
if (!hasAnyDisabled) {
|
|
1119
1131
|
for (const key in newVariants) {
|
|
1120
1132
|
if (!Object.hasOwn(newVariants, key)) continue;
|
|
1121
1133
|
const value = (newVariants as Record<string, unknown>)[key];
|
|
1122
1134
|
setChangedVariant(key, value, true);
|
|
1123
|
-
if (getCurrentVariantValue(key)
|
|
1135
|
+
if (Object.is(getCurrentVariantValue(key), value)) continue;
|
|
1124
1136
|
ensureUpdated()[key] = value;
|
|
1125
1137
|
}
|
|
1126
1138
|
return;
|
|
@@ -1139,33 +1151,7 @@ export function create({
|
|
|
1139
1151
|
}
|
|
1140
1152
|
}
|
|
1141
1153
|
setChangedVariant(key, value, true);
|
|
1142
|
-
if (getCurrentVariantValue(key)
|
|
1143
|
-
ensureUpdated()[key] = value;
|
|
1144
|
-
}
|
|
1145
|
-
},
|
|
1146
|
-
setDefaultVariants: (
|
|
1147
|
-
newDefaults: VariantValues<Record<string, unknown>>,
|
|
1148
|
-
) => {
|
|
1149
|
-
for (const key in newDefaults) {
|
|
1150
|
-
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
1151
|
-
if (userVariantProps[key] !== undefined) continue;
|
|
1152
|
-
if (protectedVariantKeys?.has(key)) continue;
|
|
1153
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
1154
|
-
if (hasAnyDisabled) {
|
|
1155
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1156
|
-
const valueKey = getVariantValueKey(value);
|
|
1157
|
-
if (
|
|
1158
|
-
valueKey != null &&
|
|
1159
|
-
disabledVariantValues[key]?.has(valueKey)
|
|
1160
|
-
) {
|
|
1161
|
-
continue;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
setChangedVariant(key, value);
|
|
1165
|
-
if (pendingProtectedVariants) {
|
|
1166
|
-
pendingProtectedVariants[key] = value;
|
|
1167
|
-
}
|
|
1168
|
-
if (getCurrentVariantValue(key) === value) continue;
|
|
1154
|
+
if (Object.is(getCurrentVariantValue(key), value)) continue;
|
|
1169
1155
|
ensureUpdated()[key] = value;
|
|
1170
1156
|
}
|
|
1171
1157
|
},
|
|
@@ -1232,37 +1218,17 @@ export function create({
|
|
|
1232
1218
|
protectedVariants,
|
|
1233
1219
|
pendingProtectedVariants,
|
|
1234
1220
|
protectedVariantKeys,
|
|
1221
|
+
defaultResolved = resolved,
|
|
1222
|
+
renderOnly = false,
|
|
1235
1223
|
) => {
|
|
1236
|
-
// Run `refine` (if any). May modify resolved variants and emit classes
|
|
1237
|
-
// and styles.
|
|
1238
1224
|
let workingResolved = resolved;
|
|
1239
1225
|
let cClasses: ClassValue[] | null = null;
|
|
1240
1226
|
let cStyle: StyleValue | null = null;
|
|
1241
1227
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1242
|
-
if (refine) {
|
|
1243
|
-
const refineResult = runRefineContext(
|
|
1244
|
-
resolved,
|
|
1245
|
-
userVariantProps,
|
|
1246
|
-
true,
|
|
1247
|
-
true,
|
|
1248
|
-
protectedVariants,
|
|
1249
|
-
pendingProtectedVariants,
|
|
1250
|
-
protectedVariantKeys,
|
|
1251
|
-
);
|
|
1252
|
-
workingResolved = refineResult.workingResolved;
|
|
1253
|
-
cClasses = refineResult.classes;
|
|
1254
|
-
cStyle = refineResult.style;
|
|
1255
|
-
changedVariants = refineResult.changedVariants;
|
|
1256
|
-
}
|
|
1257
1228
|
|
|
1258
1229
|
// Run extends' contributions first (their full classes + styles) so our
|
|
1259
1230
|
// own base style and variants apply on top, matching the original
|
|
1260
1231
|
// ext1 → ext2 → … → current ordering.
|
|
1261
|
-
//
|
|
1262
|
-
// Pass explicit user values plus refine changes as the extends'
|
|
1263
|
-
// `userVariantProps`. This lets more-specific refine decisions stick
|
|
1264
|
-
// across re-runs while inherited static defaults can still be refined by
|
|
1265
|
-
// the extended component's own refine chain.
|
|
1266
1232
|
if (hasExtend) {
|
|
1267
1233
|
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
1268
1234
|
// caller-provided sets need merging.
|
|
@@ -1327,6 +1293,8 @@ export function create({
|
|
|
1327
1293
|
protectedVariants,
|
|
1328
1294
|
pendingProtectedVariants,
|
|
1329
1295
|
protectedVariantKeys,
|
|
1296
|
+
defaultResolved,
|
|
1297
|
+
renderOnly,
|
|
1330
1298
|
);
|
|
1331
1299
|
if (extClasses.length > 0) {
|
|
1332
1300
|
const joined = clsx(extClasses);
|
|
@@ -1348,8 +1316,14 @@ export function create({
|
|
|
1348
1316
|
protectedVariants,
|
|
1349
1317
|
pendingProtectedVariants,
|
|
1350
1318
|
protectedVariantKeys,
|
|
1319
|
+
defaultResolved,
|
|
1320
|
+
renderOnly,
|
|
1351
1321
|
);
|
|
1352
1322
|
}
|
|
1323
|
+
workingResolved = filterOwnDisabledVariants(
|
|
1324
|
+
workingResolved,
|
|
1325
|
+
defaultResolved,
|
|
1326
|
+
);
|
|
1353
1327
|
// Only sync protected variants when a child refine resolver can
|
|
1354
1328
|
// observe them. Otherwise extUserVariantProps may alias caller props.
|
|
1355
1329
|
if (protectedVariants && extMetasWithRefineCount > 0) {
|
|
@@ -1358,6 +1332,42 @@ export function create({
|
|
|
1358
1332
|
}
|
|
1359
1333
|
}
|
|
1360
1334
|
|
|
1335
|
+
// Run own computed defaults after extended components so defaults resolve
|
|
1336
|
+
// from base to child. They still run before this component's `refine`.
|
|
1337
|
+
if (!renderOnly && computedDefaultCount > 0) {
|
|
1338
|
+
const computedResult = runComputedDefaults(
|
|
1339
|
+
workingResolved,
|
|
1340
|
+
defaultResolved,
|
|
1341
|
+
userVariantProps,
|
|
1342
|
+
true,
|
|
1343
|
+
protectedVariantKeys,
|
|
1344
|
+
);
|
|
1345
|
+
workingResolved = computedResult.workingResolved;
|
|
1346
|
+
changedVariants = computedResult.changedVariants;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Run own `refine` (if any). May modify resolved variants and emit
|
|
1350
|
+
// classes and styles that are applied after this component's variants.
|
|
1351
|
+
if (refine) {
|
|
1352
|
+
const refineResult = runRefineContext(
|
|
1353
|
+
workingResolved,
|
|
1354
|
+
userVariantProps,
|
|
1355
|
+
true,
|
|
1356
|
+
true,
|
|
1357
|
+
!renderOnly,
|
|
1358
|
+
protectedVariants,
|
|
1359
|
+
pendingProtectedVariants,
|
|
1360
|
+
protectedVariantKeys,
|
|
1361
|
+
);
|
|
1362
|
+
workingResolved = refineResult.workingResolved;
|
|
1363
|
+
cClasses = refineResult.classes;
|
|
1364
|
+
cStyle = refineResult.style;
|
|
1365
|
+
if (refineResult.changedVariants) {
|
|
1366
|
+
changedVariants ??= {};
|
|
1367
|
+
Object.assign(changedVariants, refineResult.changedVariants);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1361
1371
|
// Apply own base style (after extends' styles, matching original order).
|
|
1362
1372
|
if (hasBaseStyle) {
|
|
1363
1373
|
Object.assign(styleOut, baseStyle);
|
|
@@ -1456,7 +1466,7 @@ export function create({
|
|
|
1456
1466
|
};
|
|
1457
1467
|
|
|
1458
1468
|
const compute: ComputeFn =
|
|
1459
|
-
!refine && extMetasWithRefineCount === 0
|
|
1469
|
+
!refine && computedDefaultCount === 0 && extMetasWithRefineCount === 0
|
|
1460
1470
|
? computeOnce
|
|
1461
1471
|
: (
|
|
1462
1472
|
resolved,
|
|
@@ -1469,16 +1479,32 @@ export function create({
|
|
|
1469
1479
|
protectedVariants,
|
|
1470
1480
|
pendingProtectedVariants,
|
|
1471
1481
|
protectedVariantKeys,
|
|
1482
|
+
incomingDefaultResolved = resolved,
|
|
1483
|
+
renderOnly = false,
|
|
1472
1484
|
) => {
|
|
1485
|
+
if (renderOnly) {
|
|
1486
|
+
return computeOnce(
|
|
1487
|
+
resolved,
|
|
1488
|
+
userVariantProps,
|
|
1489
|
+
skipKeys,
|
|
1490
|
+
skipValues,
|
|
1491
|
+
classesOut,
|
|
1492
|
+
styleOut,
|
|
1493
|
+
runState,
|
|
1494
|
+
protectedVariants,
|
|
1495
|
+
pendingProtectedVariants,
|
|
1496
|
+
protectedVariantKeys,
|
|
1497
|
+
incomingDefaultResolved,
|
|
1498
|
+
true,
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1473
1501
|
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
1474
1502
|
protectedVariants ??= {};
|
|
1475
1503
|
protectedVariantKeys ??= new Set<string>();
|
|
1476
1504
|
let workingResolved = resolved;
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1479
|
-
|
|
1480
|
-
// Lazy-init keeps convergent loops allocation-free.
|
|
1481
|
-
let unstableKeys: Set<string> | null = null;
|
|
1505
|
+
// Latest variant changes from non-converging iterations inside the
|
|
1506
|
+
// tracking window. Lazy-init keeps convergent loops allocation-free.
|
|
1507
|
+
let unstableChanges: Map<string, VariantChange> | null = null;
|
|
1482
1508
|
let lastClasses: ClsxClassValue[] = [];
|
|
1483
1509
|
let lastStyle: StyleValue = {};
|
|
1484
1510
|
let isFirstRun = true;
|
|
@@ -1500,6 +1526,10 @@ export function create({
|
|
|
1500
1526
|
? classesOut
|
|
1501
1527
|
: [];
|
|
1502
1528
|
const nextStyle: StyleValue = useDirectOutput ? styleOut : {};
|
|
1529
|
+
const defaultResolved = mergeProtectedIntoBase(
|
|
1530
|
+
incomingDefaultResolved,
|
|
1531
|
+
protectedVariants,
|
|
1532
|
+
);
|
|
1503
1533
|
const nextResolved = computeOnce(
|
|
1504
1534
|
workingResolved,
|
|
1505
1535
|
userVariantProps,
|
|
@@ -1511,6 +1541,7 @@ export function create({
|
|
|
1511
1541
|
protectedVariants,
|
|
1512
1542
|
nextPendingProtectedVariants,
|
|
1513
1543
|
protectedVariantKeys,
|
|
1544
|
+
defaultResolved,
|
|
1514
1545
|
);
|
|
1515
1546
|
|
|
1516
1547
|
let protectedChanged: boolean;
|
|
@@ -1533,7 +1564,30 @@ export function create({
|
|
|
1533
1564
|
(nextResolved === workingResolved ||
|
|
1534
1565
|
areVariantsEqual(workingResolved, nextResolved))
|
|
1535
1566
|
) {
|
|
1536
|
-
if (
|
|
1567
|
+
if (nextResolved !== workingResolved) {
|
|
1568
|
+
if (useDirectOutput) {
|
|
1569
|
+
classesOut.length = classCount;
|
|
1570
|
+
for (const key in styleOut) {
|
|
1571
|
+
if (Object.hasOwn(styleOut, key)) {
|
|
1572
|
+
Reflect.deleteProperty(styleOut, key);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
computeOnce(
|
|
1577
|
+
nextResolved,
|
|
1578
|
+
userVariantProps,
|
|
1579
|
+
skipKeys,
|
|
1580
|
+
skipValues,
|
|
1581
|
+
classesOut,
|
|
1582
|
+
styleOut,
|
|
1583
|
+
runState,
|
|
1584
|
+
protectedVariants,
|
|
1585
|
+
null,
|
|
1586
|
+
protectedVariantKeys,
|
|
1587
|
+
defaultResolved,
|
|
1588
|
+
true,
|
|
1589
|
+
);
|
|
1590
|
+
} else if (!useDirectOutput) {
|
|
1537
1591
|
for (let i = 0; i < nextClasses.length; i++) {
|
|
1538
1592
|
classesOut.push(nextClasses[i]);
|
|
1539
1593
|
}
|
|
@@ -1546,11 +1600,11 @@ export function create({
|
|
|
1546
1600
|
process.env.NODE_ENV !== "production" &&
|
|
1547
1601
|
runState.remaining < REFINE_UNSTABLE_TRACKING_WINDOW
|
|
1548
1602
|
) {
|
|
1549
|
-
if (!
|
|
1550
|
-
|
|
1603
|
+
if (!unstableChanges) {
|
|
1604
|
+
unstableChanges = new Map<string, VariantChange>();
|
|
1551
1605
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1606
|
+
accumulateUnstableVariantChanges(
|
|
1607
|
+
unstableChanges,
|
|
1554
1608
|
workingResolved,
|
|
1555
1609
|
nextResolved,
|
|
1556
1610
|
);
|
|
@@ -1559,7 +1613,11 @@ export function create({
|
|
|
1559
1613
|
if (useDirectOutput && runState.remaining === 0) {
|
|
1560
1614
|
// Keep the direct output from the last allowed run. Rolling
|
|
1561
1615
|
// back here would drop it before the fallback copy below.
|
|
1562
|
-
warnRefineLimit(
|
|
1616
|
+
warnRefineLimit({
|
|
1617
|
+
runState,
|
|
1618
|
+
creationFrame,
|
|
1619
|
+
unstableChanges,
|
|
1620
|
+
});
|
|
1563
1621
|
return nextResolved;
|
|
1564
1622
|
}
|
|
1565
1623
|
|
|
@@ -1579,7 +1637,11 @@ export function create({
|
|
|
1579
1637
|
isFirstRun = false;
|
|
1580
1638
|
}
|
|
1581
1639
|
|
|
1582
|
-
warnRefineLimit(
|
|
1640
|
+
warnRefineLimit({
|
|
1641
|
+
runState,
|
|
1642
|
+
creationFrame,
|
|
1643
|
+
unstableChanges,
|
|
1644
|
+
});
|
|
1583
1645
|
|
|
1584
1646
|
for (let i = 0; i < lastClasses.length; i++) {
|
|
1585
1647
|
classesOut.push(lastClasses[i]);
|
|
@@ -1596,22 +1658,10 @@ export function create({
|
|
|
1596
1658
|
protectedVariants,
|
|
1597
1659
|
pendingProtectedVariants,
|
|
1598
1660
|
protectedVariantKeys,
|
|
1661
|
+
defaultResolved = resolved,
|
|
1599
1662
|
) => {
|
|
1600
1663
|
let workingResolved = resolved;
|
|
1601
1664
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1602
|
-
if (refine) {
|
|
1603
|
-
const refineResult = runRefineContext(
|
|
1604
|
-
resolved,
|
|
1605
|
-
userVariantProps,
|
|
1606
|
-
filterOwnVariants,
|
|
1607
|
-
false,
|
|
1608
|
-
protectedVariants,
|
|
1609
|
-
pendingProtectedVariants,
|
|
1610
|
-
protectedVariantKeys,
|
|
1611
|
-
);
|
|
1612
|
-
workingResolved = refineResult.workingResolved;
|
|
1613
|
-
changedVariants = refineResult.changedVariants;
|
|
1614
|
-
}
|
|
1615
1665
|
|
|
1616
1666
|
if (extMetasWithRefineCount > 0) {
|
|
1617
1667
|
const extUserVariantProps = getExtUserVariantProps(
|
|
@@ -1631,6 +1681,11 @@ export function create({
|
|
|
1631
1681
|
protectedVariants,
|
|
1632
1682
|
pendingProtectedVariants,
|
|
1633
1683
|
protectedVariantKeys,
|
|
1684
|
+
defaultResolved,
|
|
1685
|
+
);
|
|
1686
|
+
workingResolved = filterOwnDisabledVariants(
|
|
1687
|
+
workingResolved,
|
|
1688
|
+
defaultResolved,
|
|
1634
1689
|
);
|
|
1635
1690
|
if (protectedVariants) {
|
|
1636
1691
|
Object.assign(extUserVariantProps, protectedVariants);
|
|
@@ -1638,11 +1693,40 @@ export function create({
|
|
|
1638
1693
|
}
|
|
1639
1694
|
}
|
|
1640
1695
|
|
|
1696
|
+
if (computedDefaultCount > 0) {
|
|
1697
|
+
const computedResult = runComputedDefaults(
|
|
1698
|
+
workingResolved,
|
|
1699
|
+
defaultResolved,
|
|
1700
|
+
userVariantProps,
|
|
1701
|
+
filterOwnVariants,
|
|
1702
|
+
protectedVariantKeys,
|
|
1703
|
+
);
|
|
1704
|
+
workingResolved = computedResult.workingResolved;
|
|
1705
|
+
changedVariants = computedResult.changedVariants;
|
|
1706
|
+
}
|
|
1707
|
+
if (refine) {
|
|
1708
|
+
const refineResult = runRefineContext(
|
|
1709
|
+
workingResolved,
|
|
1710
|
+
userVariantProps,
|
|
1711
|
+
filterOwnVariants,
|
|
1712
|
+
false,
|
|
1713
|
+
true,
|
|
1714
|
+
protectedVariants,
|
|
1715
|
+
pendingProtectedVariants,
|
|
1716
|
+
protectedVariantKeys,
|
|
1717
|
+
);
|
|
1718
|
+
workingResolved = refineResult.workingResolved;
|
|
1719
|
+
if (refineResult.changedVariants) {
|
|
1720
|
+
changedVariants ??= {};
|
|
1721
|
+
Object.assign(changedVariants, refineResult.changedVariants);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1641
1725
|
return workingResolved;
|
|
1642
1726
|
};
|
|
1643
1727
|
|
|
1644
1728
|
const resolveRefine: ResolveRefineFn | null =
|
|
1645
|
-
refine || extMetasWithRefineCount > 0
|
|
1729
|
+
refine || computedDefaultCount > 0 || extMetasWithRefineCount > 0
|
|
1646
1730
|
? (
|
|
1647
1731
|
resolved,
|
|
1648
1732
|
userVariantProps,
|
|
@@ -1651,21 +1735,25 @@ export function create({
|
|
|
1651
1735
|
protectedVariants,
|
|
1652
1736
|
pendingProtectedVariants,
|
|
1653
1737
|
protectedVariantKeys,
|
|
1738
|
+
incomingDefaultResolved = resolved,
|
|
1654
1739
|
) => {
|
|
1655
1740
|
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
1656
1741
|
protectedVariants ??= {};
|
|
1657
1742
|
protectedVariantKeys ??= new Set<string>();
|
|
1658
1743
|
let workingResolved = resolved;
|
|
1659
|
-
//
|
|
1660
|
-
//
|
|
1661
|
-
//
|
|
1662
|
-
|
|
1663
|
-
let unstableKeys: Set<string> | null = null;
|
|
1744
|
+
// Latest variant changes from non-converging iterations inside the
|
|
1745
|
+
// tracking window. See the compute loop above for the shared
|
|
1746
|
+
// rationale.
|
|
1747
|
+
let unstableChanges: Map<string, VariantChange> | null = null;
|
|
1664
1748
|
let reachedLimit = true;
|
|
1665
1749
|
|
|
1666
1750
|
while (runState.remaining > 0) {
|
|
1667
1751
|
runState.remaining -= 1;
|
|
1668
1752
|
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1753
|
+
const defaultResolved = mergeProtectedIntoBase(
|
|
1754
|
+
incomingDefaultResolved,
|
|
1755
|
+
protectedVariants,
|
|
1756
|
+
);
|
|
1669
1757
|
const nextResolved = resolveRefineOnce(
|
|
1670
1758
|
workingResolved,
|
|
1671
1759
|
userVariantProps,
|
|
@@ -1674,6 +1762,7 @@ export function create({
|
|
|
1674
1762
|
protectedVariants,
|
|
1675
1763
|
nextPendingProtectedVariants,
|
|
1676
1764
|
protectedVariantKeys,
|
|
1765
|
+
defaultResolved,
|
|
1677
1766
|
);
|
|
1678
1767
|
let protectedChanged: boolean;
|
|
1679
1768
|
if (pendingProtectedVariants) {
|
|
@@ -1704,11 +1793,11 @@ export function create({
|
|
|
1704
1793
|
process.env.NODE_ENV !== "production" &&
|
|
1705
1794
|
runState.remaining < REFINE_UNSTABLE_TRACKING_WINDOW
|
|
1706
1795
|
) {
|
|
1707
|
-
if (!
|
|
1708
|
-
|
|
1796
|
+
if (!unstableChanges) {
|
|
1797
|
+
unstableChanges = new Map<string, VariantChange>();
|
|
1709
1798
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1799
|
+
accumulateUnstableVariantChanges(
|
|
1800
|
+
unstableChanges,
|
|
1712
1801
|
workingResolved,
|
|
1713
1802
|
nextResolved,
|
|
1714
1803
|
);
|
|
@@ -1717,7 +1806,11 @@ export function create({
|
|
|
1717
1806
|
}
|
|
1718
1807
|
|
|
1719
1808
|
if (reachedLimit) {
|
|
1720
|
-
warnRefineLimit(
|
|
1809
|
+
warnRefineLimit({
|
|
1810
|
+
runState,
|
|
1811
|
+
creationFrame,
|
|
1812
|
+
unstableChanges,
|
|
1813
|
+
});
|
|
1721
1814
|
}
|
|
1722
1815
|
|
|
1723
1816
|
return workingResolved;
|
|
@@ -1731,17 +1824,11 @@ export function create({
|
|
|
1731
1824
|
): { className: string; style: StyleValue } => {
|
|
1732
1825
|
const propsRecord = props as Record<string, unknown>;
|
|
1733
1826
|
|
|
1734
|
-
// Inline resolve: avoids allocating a separate variantProps object for
|
|
1735
|
-
// the common case where no extends need a resolveDefaults pass.
|
|
1736
|
-
// resolveVariantsHot would also work here but assumes its input is
|
|
1737
|
-
// variant-only (it uses for-in for speed).
|
|
1738
1827
|
let resolved: Record<string, unknown> = {};
|
|
1739
1828
|
Object.assign(resolved, staticDefaults);
|
|
1740
1829
|
|
|
1741
1830
|
let userVariantProps: Record<string, unknown>;
|
|
1742
|
-
if (extMetasWithRefineCount > 0) {
|
|
1743
|
-
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1744
|
-
// object as `userProps`, so we extract one.
|
|
1831
|
+
if (refine || computedDefaultCount > 0 || extMetasWithRefineCount > 0) {
|
|
1745
1832
|
const variantProps: Record<string, unknown> = {};
|
|
1746
1833
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
1747
1834
|
const key = variantKeys[i];
|
|
@@ -1749,14 +1836,6 @@ export function create({
|
|
|
1749
1836
|
variantProps[key] = propsRecord[key];
|
|
1750
1837
|
}
|
|
1751
1838
|
}
|
|
1752
|
-
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1753
|
-
const meta = extMetasWithRefine[i];
|
|
1754
|
-
const extDefaults = meta.resolveDefaults!(resolved, variantProps);
|
|
1755
|
-
for (const k in extDefaults) {
|
|
1756
|
-
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
1757
|
-
resolved[k] = extDefaults[k];
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
1839
|
for (const k in variantProps) {
|
|
1761
1840
|
if (!Object.hasOwn(variantProps, k)) continue;
|
|
1762
1841
|
const v = variantProps[k];
|
|
@@ -1854,11 +1933,11 @@ export function create({
|
|
|
1854
1933
|
const meta: ComponentMeta = {
|
|
1855
1934
|
baseClass: computedBaseClass,
|
|
1856
1935
|
staticDefaults,
|
|
1857
|
-
resolveDefaults: resolveDefaultsFn,
|
|
1858
1936
|
compute,
|
|
1859
1937
|
resolveRefine,
|
|
1860
1938
|
transformClass,
|
|
1861
1939
|
functionVariantKeys,
|
|
1940
|
+
computedDefaultKeys,
|
|
1862
1941
|
};
|
|
1863
1942
|
|
|
1864
1943
|
const initComponent = <
|
|
@@ -1930,6 +2009,4 @@ export function create({
|
|
|
1930
2009
|
return { cv, cx };
|
|
1931
2010
|
}
|
|
1932
2011
|
|
|
1933
|
-
function noop() {}
|
|
1934
|
-
|
|
1935
2012
|
export const { cv, cx } = create();
|