clava 0.3.0 → 0.4.1
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 +86 -0
- package/README.md +38 -38
- package/dist/index.d.ts +19 -27
- package/dist/index.js +143 -86
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/index.ts +430 -215
- package/src/types.ts +38 -46
- package/src/utils.ts +31 -10
- package/tests/_utils.ts +7 -5
- package/tests/build.test.ts +81 -7
- package/tests/component-api.test.ts +28 -28
- package/tests/extend.test.ts +13 -8
- package/tests/{computed-variants.test.ts → function-variants.test.ts} +105 -25
- package/tests/prototype-pollution.test.ts +6 -6
- package/tests/{computed.test.ts → refine.test.ts} +292 -149
- package/tests/variants-inference.test.ts +252 -0
package/src/index.ts
CHANGED
|
@@ -5,14 +5,13 @@ import type {
|
|
|
5
5
|
ClassValue,
|
|
6
6
|
ComponentProps,
|
|
7
7
|
ComponentResult,
|
|
8
|
-
Computed,
|
|
9
|
-
ComputedVariants,
|
|
10
8
|
ExtendableVariants,
|
|
11
9
|
HTMLObjProps,
|
|
12
10
|
HTMLProps,
|
|
13
11
|
JSXProps,
|
|
14
12
|
MergeVariants,
|
|
15
13
|
ModalComponent,
|
|
14
|
+
Refine,
|
|
16
15
|
SplitPropsFunction,
|
|
17
16
|
StyleClassProps,
|
|
18
17
|
StyleClassValue,
|
|
@@ -42,33 +41,33 @@ type ComputeFn = (
|
|
|
42
41
|
skipValues: Record<string, Set<string>> | null,
|
|
43
42
|
classesOut: ClsxClassValue[],
|
|
44
43
|
styleOut: StyleValue,
|
|
45
|
-
runState?:
|
|
44
|
+
runState?: RefineRunState,
|
|
46
45
|
protectedVariants?: Record<string, unknown> | null,
|
|
47
46
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
48
47
|
protectedVariantKeys?: Set<string> | null,
|
|
49
48
|
) => Record<string, unknown>;
|
|
50
49
|
|
|
51
|
-
type
|
|
50
|
+
type ResolveRefineFn = (
|
|
52
51
|
resolved: Record<string, unknown>,
|
|
53
52
|
userVariantProps: Record<string, unknown>,
|
|
54
53
|
filterOwnVariants?: boolean,
|
|
55
|
-
runState?:
|
|
54
|
+
runState?: RefineRunState,
|
|
56
55
|
protectedVariants?: Record<string, unknown> | null,
|
|
57
56
|
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
58
57
|
protectedVariantKeys?: Set<string> | null,
|
|
59
58
|
) => Record<string, unknown>;
|
|
60
59
|
|
|
61
|
-
interface
|
|
60
|
+
interface RefineRunState {
|
|
62
61
|
remaining: number;
|
|
63
|
-
warned
|
|
62
|
+
warned?: boolean;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
// Internal metadata stored on components but hidden from public types.
|
|
67
66
|
interface ComponentMeta {
|
|
68
67
|
baseClass: string;
|
|
69
68
|
staticDefaults: Record<string, unknown>;
|
|
70
|
-
// Returns variants set via setDefaultVariants in the
|
|
71
|
-
// null when this component has no resolveDefaults work to do (no `
|
|
69
|
+
// Returns variants set via setDefaultVariants in the refine function chain.
|
|
70
|
+
// null when this component has no resolveDefaults work to do (no `refine`
|
|
72
71
|
// and no extends with work).
|
|
73
72
|
resolveDefaults:
|
|
74
73
|
| ((
|
|
@@ -79,22 +78,39 @@ interface ComponentMeta {
|
|
|
79
78
|
// Returns variant classes + style for this component, used by extending
|
|
80
79
|
// components. Top-level rendering also routes through this.
|
|
81
80
|
compute: ComputeFn;
|
|
82
|
-
|
|
81
|
+
resolveRefine: ResolveRefineFn | null;
|
|
83
82
|
// Reference identity is used to detect mixed-factory `extend`. When a
|
|
84
83
|
// component is extended by a parent from a different `create()` call, the
|
|
85
84
|
// parent applies this transform to the extend's contribution before joining,
|
|
86
85
|
// preserving each factory's transform boundary.
|
|
87
86
|
transformClass: (className: string) => string;
|
|
87
|
+
// Variant keys whose effective definition in this component's chain is a
|
|
88
|
+
// function. An extending component that supplies a non-function variant for
|
|
89
|
+
// the same key uses this to tell us to skip that key (matching the
|
|
90
|
+
// type-level "function variant is replaced by anything in the child" rule).
|
|
91
|
+
// Empty when no key in this chain is a function variant.
|
|
92
|
+
functionVariantKeys: Set<string>;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
const META_KEY = "__meta";
|
|
91
96
|
|
|
97
|
+
interface ComponentWithMeta {
|
|
98
|
+
[META_KEY]?: ComponentMeta;
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
93
102
|
string,
|
|
94
103
|
unknown
|
|
95
104
|
>;
|
|
96
105
|
|
|
97
|
-
const
|
|
106
|
+
const MAX_REFINE_RUNS = 50;
|
|
107
|
+
|
|
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;
|
|
98
114
|
|
|
99
115
|
function areVariantsEqual(
|
|
100
116
|
a: Record<string, unknown>,
|
|
@@ -111,16 +127,94 @@ function areVariantsEqual(
|
|
|
111
127
|
return true;
|
|
112
128
|
}
|
|
113
129
|
|
|
114
|
-
|
|
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;
|
|
115
202
|
if (runState.warned) return;
|
|
116
203
|
runState.warned = true;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
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(", ")}.`;
|
|
123
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);
|
|
124
218
|
}
|
|
125
219
|
|
|
126
220
|
function getExtUserVariantProps(
|
|
@@ -149,7 +243,9 @@ function mergeVariants(
|
|
|
149
243
|
for (const key in source) {
|
|
150
244
|
if (!Object.hasOwn(source, key)) continue;
|
|
151
245
|
const value = source[key];
|
|
152
|
-
if (!Object.is(target[key], value))
|
|
246
|
+
if (!Object.is(target[key], value)) {
|
|
247
|
+
changed = true;
|
|
248
|
+
}
|
|
153
249
|
target[key] = value;
|
|
154
250
|
}
|
|
155
251
|
return changed;
|
|
@@ -158,21 +254,22 @@ function mergeVariants(
|
|
|
158
254
|
if (!Object.hasOwn(source, key)) continue;
|
|
159
255
|
if (skipKeys.has(key)) continue;
|
|
160
256
|
const value = source[key];
|
|
161
|
-
if (!Object.is(target[key], value))
|
|
257
|
+
if (!Object.is(target[key], value)) {
|
|
258
|
+
changed = true;
|
|
259
|
+
}
|
|
162
260
|
target[key] = value;
|
|
163
261
|
}
|
|
164
262
|
return changed;
|
|
165
263
|
}
|
|
166
264
|
|
|
167
|
-
//
|
|
265
|
+
// Components carry internal metadata on a non-public property so user-facing
|
|
266
|
+
// component types stay clean.
|
|
168
267
|
function getComponentMeta(component: AnyComponent): ComponentMeta | undefined {
|
|
169
|
-
return (component as
|
|
170
|
-
| ComponentMeta
|
|
171
|
-
| undefined;
|
|
268
|
+
return (component as AnyComponent & ComponentWithMeta)[META_KEY];
|
|
172
269
|
}
|
|
173
270
|
|
|
174
271
|
function setComponentMeta(component: AnyComponent, meta: ComponentMeta): void {
|
|
175
|
-
(component as
|
|
272
|
+
(component as AnyComponent & ComponentWithMeta)[META_KEY] = meta;
|
|
176
273
|
}
|
|
177
274
|
|
|
178
275
|
export type {
|
|
@@ -202,16 +299,14 @@ export type Variant<
|
|
|
202
299
|
|
|
203
300
|
export interface CVConfig<
|
|
204
301
|
V extends Variants = {},
|
|
205
|
-
CV extends ComputedVariants = {},
|
|
206
302
|
E extends AnyComponent[] = [],
|
|
207
303
|
> {
|
|
208
304
|
extend?: E;
|
|
209
305
|
class?: ClassValue;
|
|
210
306
|
style?: StyleValue;
|
|
211
307
|
variants?: ExtendableVariants<V, E>;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
computed?: Computed<MergeVariants<V, CV, E>>;
|
|
308
|
+
defaultVariants?: VariantValues<MergeVariants<V, E>>;
|
|
309
|
+
refine?: Refine<MergeVariants<V, E>>;
|
|
215
310
|
}
|
|
216
311
|
|
|
217
312
|
interface CreateParams {
|
|
@@ -282,7 +377,7 @@ function extractClassAndStylePrebuilt(value: unknown): PrebuiltValue {
|
|
|
282
377
|
* components.
|
|
283
378
|
*/
|
|
284
379
|
function collectVariantKeys(
|
|
285
|
-
config: CVConfig<Variants,
|
|
380
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
286
381
|
): string[] {
|
|
287
382
|
const keys = new Set<string>();
|
|
288
383
|
|
|
@@ -307,32 +402,31 @@ function collectVariantKeys(
|
|
|
307
402
|
}
|
|
308
403
|
}
|
|
309
404
|
|
|
310
|
-
if (config.computedVariants) {
|
|
311
|
-
for (const key in config.computedVariants) {
|
|
312
|
-
if (!Object.hasOwn(config.computedVariants, key)) continue;
|
|
313
|
-
keys.add(key);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
405
|
return Array.from(keys);
|
|
318
406
|
}
|
|
319
407
|
|
|
320
408
|
function isVariantDisabled(
|
|
321
|
-
config: CVConfig<Variants,
|
|
409
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
322
410
|
key: string,
|
|
323
411
|
): boolean {
|
|
324
412
|
return config.variants?.[key] === null;
|
|
325
413
|
}
|
|
326
414
|
|
|
327
415
|
function getVariantValueKey(value: unknown): string | undefined {
|
|
328
|
-
if (typeof value === "string")
|
|
329
|
-
|
|
330
|
-
|
|
416
|
+
if (typeof value === "string") {
|
|
417
|
+
return value;
|
|
418
|
+
}
|
|
419
|
+
if (typeof value === "number") {
|
|
420
|
+
return String(value);
|
|
421
|
+
}
|
|
422
|
+
if (typeof value === "boolean") {
|
|
423
|
+
return String(value);
|
|
424
|
+
}
|
|
331
425
|
return undefined;
|
|
332
426
|
}
|
|
333
427
|
|
|
334
428
|
function isVariantValueDisabled(
|
|
335
|
-
config: CVConfig<Variants,
|
|
429
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
336
430
|
key: string,
|
|
337
431
|
value: unknown,
|
|
338
432
|
): boolean {
|
|
@@ -344,10 +438,12 @@ function isVariantValueDisabled(
|
|
|
344
438
|
}
|
|
345
439
|
|
|
346
440
|
function collectDisabledVariantKeys(
|
|
347
|
-
config: CVConfig<Variants,
|
|
441
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
348
442
|
): Set<string> {
|
|
349
443
|
const keys = new Set<string>();
|
|
350
|
-
if (!config.variants)
|
|
444
|
+
if (!config.variants) {
|
|
445
|
+
return keys;
|
|
446
|
+
}
|
|
351
447
|
for (const key in config.variants) {
|
|
352
448
|
if (!Object.hasOwn(config.variants, key)) continue;
|
|
353
449
|
if ((config.variants as Record<string, unknown>)[key] === null) {
|
|
@@ -358,10 +454,12 @@ function collectDisabledVariantKeys(
|
|
|
358
454
|
}
|
|
359
455
|
|
|
360
456
|
function collectDisabledVariantValues(
|
|
361
|
-
config: CVConfig<Variants,
|
|
457
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
362
458
|
): Record<string, Set<string>> {
|
|
363
459
|
const values: Record<string, Set<string>> = {};
|
|
364
|
-
if (!config.variants)
|
|
460
|
+
if (!config.variants) {
|
|
461
|
+
return values;
|
|
462
|
+
}
|
|
365
463
|
for (const key in config.variants) {
|
|
366
464
|
if (!Object.hasOwn(config.variants, key)) continue;
|
|
367
465
|
const variant = (config.variants as Record<string, unknown>)[key];
|
|
@@ -401,14 +499,22 @@ function normalizeKeySource(source: unknown): NormalizedSource {
|
|
|
401
499
|
};
|
|
402
500
|
}
|
|
403
501
|
|
|
404
|
-
if (!source)
|
|
502
|
+
if (!source) {
|
|
503
|
+
return EMPTY_SOURCE;
|
|
504
|
+
}
|
|
405
505
|
if (typeof source !== "object" && typeof source !== "function") {
|
|
406
506
|
return EMPTY_SOURCE;
|
|
407
507
|
}
|
|
408
508
|
const typed = source as Record<string, unknown>;
|
|
409
|
-
if (typeof typed.getVariants !== "function")
|
|
410
|
-
|
|
411
|
-
|
|
509
|
+
if (typeof typed.getVariants !== "function") {
|
|
510
|
+
return EMPTY_SOURCE;
|
|
511
|
+
}
|
|
512
|
+
if (!Array.isArray(typed.propKeys)) {
|
|
513
|
+
return EMPTY_SOURCE;
|
|
514
|
+
}
|
|
515
|
+
if (!Array.isArray(typed.variantKeys)) {
|
|
516
|
+
return EMPTY_SOURCE;
|
|
517
|
+
}
|
|
412
518
|
|
|
413
519
|
return {
|
|
414
520
|
propKeys: typed.propKeys as string[],
|
|
@@ -544,7 +650,9 @@ function buildPrebuiltVariant(variantDef: unknown): PrebuiltVariant {
|
|
|
544
650
|
if (!Object.hasOwn(variantDef, key)) continue;
|
|
545
651
|
const value = variantDef[key];
|
|
546
652
|
if (value === null) {
|
|
547
|
-
if (!disabledValues)
|
|
653
|
+
if (!disabledValues) {
|
|
654
|
+
disabledValues = new Set<string>();
|
|
655
|
+
}
|
|
548
656
|
disabledValues.add(key);
|
|
549
657
|
continue;
|
|
550
658
|
}
|
|
@@ -565,14 +673,10 @@ export function create({
|
|
|
565
673
|
}: CreateParams = {}) {
|
|
566
674
|
const cx = (...classes: ClsxClassValue[]) => transformClass(clsx(...classes));
|
|
567
675
|
|
|
568
|
-
const cv = <
|
|
569
|
-
V
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
>(
|
|
573
|
-
config: CVConfig<V, CV, E> = {},
|
|
574
|
-
): CVComponent<V, CV, E> => {
|
|
575
|
-
type MergedVariants = MergeVariants<V, CV, E>;
|
|
676
|
+
const cv = <V extends Variants = {}, const E extends AnyComponent[] = []>(
|
|
677
|
+
config: CVConfig<V, E> = {},
|
|
678
|
+
): CVComponent<V, E> => {
|
|
679
|
+
type MergedVariants = MergeVariants<V, E>;
|
|
576
680
|
|
|
577
681
|
// ----- Pre-computed at creation time -----
|
|
578
682
|
const variantKeys = collectVariantKeys(config);
|
|
@@ -589,41 +693,34 @@ export function create({
|
|
|
589
693
|
const extend = config.extend;
|
|
590
694
|
const hasExtend = !!extend && extend.length > 0;
|
|
591
695
|
const variants = config.variants;
|
|
592
|
-
const
|
|
593
|
-
const computed = config.computed;
|
|
696
|
+
const refine = config.refine;
|
|
594
697
|
const baseStyle = config.style;
|
|
595
698
|
const hasBaseStyle = !!baseStyle;
|
|
596
699
|
|
|
597
|
-
//
|
|
598
|
-
//
|
|
700
|
+
// Split `variants` entries into static entries (object/shorthand) and
|
|
701
|
+
// function-variant entries. Static entries are pre-built into
|
|
702
|
+
// PrebuiltVariant for fast iteration. Function-variant entries override
|
|
703
|
+
// any same-key inherited variant (see `staticExtSkipKeys`).
|
|
599
704
|
const variantEntryNames: string[] = [];
|
|
600
705
|
const variantEntryDefs: PrebuiltVariant[] = [];
|
|
706
|
+
const functionVariantNames: string[] = [];
|
|
707
|
+
const functionVariantFns: Array<(value: unknown) => unknown> = [];
|
|
601
708
|
if (variants) {
|
|
602
709
|
for (const name in variants) {
|
|
603
710
|
if (!Object.hasOwn(variants, name)) continue;
|
|
604
711
|
const variant = (variants as Record<string, unknown>)[name];
|
|
605
712
|
if (variant === null) continue;
|
|
713
|
+
if (typeof variant === "function") {
|
|
714
|
+
functionVariantNames.push(name);
|
|
715
|
+
functionVariantFns.push(variant as (value: unknown) => unknown);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
606
718
|
variantEntryNames.push(name);
|
|
607
719
|
variantEntryDefs.push(buildPrebuiltVariant(variant));
|
|
608
720
|
}
|
|
609
721
|
}
|
|
610
722
|
const variantEntryCount = variantEntryNames.length;
|
|
611
|
-
|
|
612
|
-
// Pre-built computed-variants entries.
|
|
613
|
-
const computedVariantNames: string[] = [];
|
|
614
|
-
const computedVariantFns: Array<(value: unknown) => unknown> = [];
|
|
615
|
-
if (computedVariantsCfg) {
|
|
616
|
-
for (const name in computedVariantsCfg) {
|
|
617
|
-
if (!Object.hasOwn(computedVariantsCfg, name)) continue;
|
|
618
|
-
computedVariantNames.push(name);
|
|
619
|
-
computedVariantFns.push(
|
|
620
|
-
(computedVariantsCfg as Record<string, (value: unknown) => unknown>)[
|
|
621
|
-
name
|
|
622
|
-
] as (value: unknown) => unknown,
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const computedVariantCount = computedVariantNames.length;
|
|
723
|
+
const functionVariantCount = functionVariantNames.length;
|
|
627
724
|
|
|
628
725
|
// Pre-compute static defaults. Includes:
|
|
629
726
|
// - extended components' static defaults
|
|
@@ -634,7 +731,9 @@ export function create({
|
|
|
634
731
|
if (extend) {
|
|
635
732
|
for (const ext of extend) {
|
|
636
733
|
const meta = getComponentMeta(ext);
|
|
637
|
-
if (meta)
|
|
734
|
+
if (meta) {
|
|
735
|
+
Object.assign(staticDefaults, meta.staticDefaults);
|
|
736
|
+
}
|
|
638
737
|
}
|
|
639
738
|
}
|
|
640
739
|
if (variants) {
|
|
@@ -702,28 +801,94 @@ export function create({
|
|
|
702
801
|
}
|
|
703
802
|
const extCount = extMetas.length;
|
|
704
803
|
|
|
705
|
-
// Filter to only extends with
|
|
706
|
-
// and `
|
|
804
|
+
// Filter to only extends with refine work in their chain. `resolveDefaults`
|
|
805
|
+
// and `resolveRefine` are populated from the same transitive condition,
|
|
707
806
|
// so one bucket is enough for both resolver paths.
|
|
708
|
-
const
|
|
807
|
+
const extMetasWithRefine: ComponentMeta[] = [];
|
|
709
808
|
for (let i = 0; i < extCount; i++) {
|
|
710
809
|
const meta = extMetas[i];
|
|
711
810
|
if (meta.resolveDefaults) {
|
|
712
|
-
|
|
811
|
+
extMetasWithRefine.push(meta);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
const extMetasWithRefineCount = extMetasWithRefine.length;
|
|
815
|
+
const shouldCollectChangedVariants = extMetasWithRefineCount > 0;
|
|
816
|
+
|
|
817
|
+
// Call-site frame captured at the `cv()` call site so refine-limit warnings
|
|
818
|
+
// can point developers at the component definition. Skipped entirely for
|
|
819
|
+
// components that can never enter the refine loop, and stripped in
|
|
820
|
+
// production via the NODE_ENV guard inside `captureCreationFrame`. The
|
|
821
|
+
// frame is captured at creation time but the underlying `.stack` string is
|
|
822
|
+
// formatted lazily on first access, so component creation stays cheap
|
|
823
|
+
// unless the warning actually fires.
|
|
824
|
+
const canTriggerRefineWarning = !!refine || extMetasWithRefineCount > 0;
|
|
825
|
+
const creationFrame = canTriggerRefineWarning
|
|
826
|
+
? captureCreationFrame(cv)
|
|
827
|
+
: undefined;
|
|
828
|
+
|
|
829
|
+
// Function variant keys inherited from extends, filtered through this
|
|
830
|
+
// component's own variants: a static (object/shorthand) variant in this
|
|
831
|
+
// component replaces an inherited function variant for the same key.
|
|
832
|
+
// The closure is exposed on `ComponentMeta` so any further extending
|
|
833
|
+
// component can detect "ancestor's effective variant for K is a function"
|
|
834
|
+
// and skip it when overriding K with a non-function.
|
|
835
|
+
const functionVariantKeys = new Set<string>();
|
|
836
|
+
for (let i = 0; i < extCount; i++) {
|
|
837
|
+
const fnKeys = extMetas[i].functionVariantKeys;
|
|
838
|
+
for (const k of fnKeys) {
|
|
839
|
+
if (disabledVariantKeys.has(k)) continue;
|
|
840
|
+
functionVariantKeys.add(k);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
844
|
+
functionVariantKeys.add(functionVariantNames[i]);
|
|
845
|
+
}
|
|
846
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
847
|
+
// A static variant in this component replaces an inherited function
|
|
848
|
+
// variant for the same key; from this component onward, the key is no
|
|
849
|
+
// longer a function variant.
|
|
850
|
+
functionVariantKeys.delete(variantEntryNames[i]);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Static-variant keys in this component that override an inherited
|
|
854
|
+
// function variant. Type-level merge says child fully replaces, so the
|
|
855
|
+
// ancestor's function must not run with the child's (object-typed) value.
|
|
856
|
+
let staticVariantsOverridingExtFn: string[] | null = null;
|
|
857
|
+
if (variantEntryCount > 0 && extCount > 0) {
|
|
858
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
859
|
+
const name = variantEntryNames[i];
|
|
860
|
+
for (let j = 0; j < extCount; j++) {
|
|
861
|
+
if (extMetas[j].functionVariantKeys.has(name)) {
|
|
862
|
+
if (!staticVariantsOverridingExtFn) {
|
|
863
|
+
staticVariantsOverridingExtFn = [];
|
|
864
|
+
}
|
|
865
|
+
staticVariantsOverridingExtFn.push(name);
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
713
869
|
}
|
|
714
870
|
}
|
|
715
|
-
const extMetasWithComputedCount = extMetasWithComputed.length;
|
|
716
|
-
const shouldCollectChangedVariants = extMetasWithComputedCount > 0;
|
|
717
871
|
|
|
718
872
|
// Pre-compute static skip key/value sets to pass to extends. These never
|
|
719
873
|
// change across calls — when caller passes no skip sets, we reuse the same
|
|
720
874
|
// object and avoid Set allocation.
|
|
721
875
|
let staticExtSkipKeys: Set<string> | null = null;
|
|
722
|
-
if (
|
|
876
|
+
if (
|
|
877
|
+
hasDisabledVariantKeys ||
|
|
878
|
+
functionVariantCount > 0 ||
|
|
879
|
+
staticVariantsOverridingExtFn !== null
|
|
880
|
+
) {
|
|
723
881
|
staticExtSkipKeys = new Set<string>();
|
|
724
|
-
for (const k of disabledVariantKeys)
|
|
725
|
-
|
|
726
|
-
|
|
882
|
+
for (const k of disabledVariantKeys) {
|
|
883
|
+
staticExtSkipKeys.add(k);
|
|
884
|
+
}
|
|
885
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
886
|
+
staticExtSkipKeys.add(functionVariantNames[i]);
|
|
887
|
+
}
|
|
888
|
+
if (staticVariantsOverridingExtFn) {
|
|
889
|
+
for (const k of staticVariantsOverridingExtFn) {
|
|
890
|
+
staticExtSkipKeys.add(k);
|
|
891
|
+
}
|
|
727
892
|
}
|
|
728
893
|
}
|
|
729
894
|
// Skip values are passed directly to extends. We can reuse the same object
|
|
@@ -740,7 +905,9 @@ export function create({
|
|
|
740
905
|
): void {
|
|
741
906
|
if (!hasAnyDisabled) {
|
|
742
907
|
for (const key in input) {
|
|
743
|
-
if (Object.hasOwn(input, key))
|
|
908
|
+
if (Object.hasOwn(input, key)) {
|
|
909
|
+
out[key] = input[key];
|
|
910
|
+
}
|
|
744
911
|
}
|
|
745
912
|
return;
|
|
746
913
|
}
|
|
@@ -760,12 +927,12 @@ export function create({
|
|
|
760
927
|
|
|
761
928
|
// Pre-create resolveDefaults function — used by parents during their
|
|
762
929
|
// `resolveVariantsHot`. Returns the variants set via setDefaultVariants in
|
|
763
|
-
// the
|
|
930
|
+
// the refine function chain.
|
|
764
931
|
//
|
|
765
|
-
// When this component has no `
|
|
932
|
+
// When this component has no `refine` and no `extend` with work, the
|
|
766
933
|
// function is null — callers can skip iterating it entirely.
|
|
767
934
|
const resolveDefaultsFn: ComponentMeta["resolveDefaults"] =
|
|
768
|
-
|
|
935
|
+
refine || extMetasWithRefineCount > 0
|
|
769
936
|
? (
|
|
770
937
|
childDefaults: Record<string, unknown>,
|
|
771
938
|
userProps: Record<string, unknown> = EMPTY_DEFAULTS,
|
|
@@ -787,21 +954,21 @@ export function create({
|
|
|
787
954
|
resolvedVariants[key] = v;
|
|
788
955
|
}
|
|
789
956
|
|
|
790
|
-
const
|
|
957
|
+
const refineDefaults: Record<string, unknown> = {};
|
|
791
958
|
|
|
792
|
-
for (let i = 0; i <
|
|
793
|
-
const extDefaults =
|
|
959
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
960
|
+
const extDefaults = extMetasWithRefine[i].resolveDefaults!(
|
|
794
961
|
childDefaults,
|
|
795
962
|
userProps,
|
|
796
963
|
);
|
|
797
964
|
for (const k in extDefaults) {
|
|
798
965
|
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
799
|
-
|
|
966
|
+
refineDefaults[k] = extDefaults[k];
|
|
800
967
|
}
|
|
801
968
|
}
|
|
802
969
|
|
|
803
|
-
if (
|
|
804
|
-
// Filter to own variant keys so `
|
|
970
|
+
if (refine) {
|
|
971
|
+
// Filter to own variant keys so `ctx.variants` matches
|
|
805
972
|
// `VariantValues<V>` when this component is used as an extend by
|
|
806
973
|
// a parent that adds extra variant keys (those keys would
|
|
807
974
|
// otherwise leak through `userProps`).
|
|
@@ -812,7 +979,7 @@ export function create({
|
|
|
812
979
|
ownVariants[k] = resolvedVariants[k];
|
|
813
980
|
}
|
|
814
981
|
}
|
|
815
|
-
|
|
982
|
+
refine({
|
|
816
983
|
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
817
984
|
setVariants: noop,
|
|
818
985
|
setDefaultVariants: (newDefaults) => {
|
|
@@ -822,7 +989,7 @@ export function create({
|
|
|
822
989
|
if (userProps[key] !== undefined) continue;
|
|
823
990
|
if (isVariantDisabled(config, key)) continue;
|
|
824
991
|
if (isVariantValueDisabled(config, key, value)) continue;
|
|
825
|
-
|
|
992
|
+
refineDefaults[key] = value;
|
|
826
993
|
}
|
|
827
994
|
},
|
|
828
995
|
addClass: noop,
|
|
@@ -830,12 +997,12 @@ export function create({
|
|
|
830
997
|
});
|
|
831
998
|
}
|
|
832
999
|
|
|
833
|
-
return
|
|
1000
|
+
return refineDefaults;
|
|
834
1001
|
}
|
|
835
1002
|
: null;
|
|
836
1003
|
|
|
837
1004
|
// Hot path: resolve variants by merging static defaults + extends'
|
|
838
|
-
//
|
|
1005
|
+
// refine defaults + user-provided props.
|
|
839
1006
|
function resolveVariantsHot(
|
|
840
1007
|
propsVariants: Record<string, unknown>,
|
|
841
1008
|
): Record<string, unknown> {
|
|
@@ -843,14 +1010,14 @@ export function create({
|
|
|
843
1010
|
const defaults: Record<string, unknown> = {};
|
|
844
1011
|
Object.assign(defaults, staticDefaults);
|
|
845
1012
|
|
|
846
|
-
// Apply
|
|
1013
|
+
// Apply refine defaults from extended components (only those that have
|
|
847
1014
|
// actual work to do).
|
|
848
|
-
for (let i = 0; i <
|
|
849
|
-
const meta =
|
|
850
|
-
const
|
|
851
|
-
for (const k in
|
|
852
|
-
if (!Object.hasOwn(
|
|
853
|
-
defaults[k] =
|
|
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];
|
|
854
1021
|
}
|
|
855
1022
|
}
|
|
856
1023
|
|
|
@@ -864,7 +1031,9 @@ export function create({
|
|
|
864
1031
|
defaults[k] = v;
|
|
865
1032
|
}
|
|
866
1033
|
|
|
867
|
-
if (!hasAnyDisabled)
|
|
1034
|
+
if (!hasAnyDisabled) {
|
|
1035
|
+
return defaults;
|
|
1036
|
+
}
|
|
868
1037
|
|
|
869
1038
|
// Filter disabled
|
|
870
1039
|
const result: Record<string, unknown> = {};
|
|
@@ -872,7 +1041,7 @@ export function create({
|
|
|
872
1041
|
return result;
|
|
873
1042
|
}
|
|
874
1043
|
|
|
875
|
-
const
|
|
1044
|
+
const runRefineContext = (
|
|
876
1045
|
resolved: Record<string, unknown>,
|
|
877
1046
|
userVariantProps: Record<string, unknown>,
|
|
878
1047
|
filterOwnVariants: boolean,
|
|
@@ -891,28 +1060,32 @@ export function create({
|
|
|
891
1060
|
let cStyle: StyleValue | null = null;
|
|
892
1061
|
let changedVariants: Record<string, unknown> | null = null;
|
|
893
1062
|
|
|
894
|
-
if (
|
|
1063
|
+
if (refine) {
|
|
895
1064
|
let ownVariants = resolved;
|
|
896
1065
|
if (filterOwnVariants) {
|
|
897
1066
|
// When this component is being extended, `resolved` is the parent's
|
|
898
1067
|
// workingResolved (a superset of our variant keys). Filter to our own
|
|
899
|
-
// keys for `ctx.variants` so the user's `
|
|
1068
|
+
// keys for `ctx.variants` so the user's `refine` callback sees the
|
|
900
1069
|
// shape declared by `VariantValues<V>` and not foreign parent keys.
|
|
901
1070
|
const filteredVariants: Record<string, unknown> = {};
|
|
902
1071
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
903
1072
|
const k = variantKeys[i];
|
|
904
|
-
if (Object.hasOwn(resolved, k))
|
|
1073
|
+
if (Object.hasOwn(resolved, k)) {
|
|
1074
|
+
filteredVariants[k] = resolved[k];
|
|
1075
|
+
}
|
|
905
1076
|
}
|
|
906
1077
|
ownVariants = filteredVariants;
|
|
907
1078
|
}
|
|
908
|
-
// Lazy-init updatedVariants — many
|
|
909
|
-
// or call setDefaultVariants for keys the user already set,
|
|
910
|
-
// copy is unnecessary in the common case.
|
|
1079
|
+
// Lazy-init updatedVariants — many refine callbacks only inspect
|
|
1080
|
+
// `variants` or call setDefaultVariants for keys the user already set,
|
|
1081
|
+
// so the copy is unnecessary in the common case.
|
|
911
1082
|
let updatedVariants: Record<string, unknown> | null = null;
|
|
912
1083
|
const localCClasses: ClassValue[] | null = collectOutput ? [] : null;
|
|
913
1084
|
let localCStyle: StyleValue | null = null;
|
|
914
1085
|
const ensureUpdated = (): Record<string, unknown> => {
|
|
915
|
-
if (updatedVariants)
|
|
1086
|
+
if (updatedVariants) {
|
|
1087
|
+
return updatedVariants;
|
|
1088
|
+
}
|
|
916
1089
|
const u: Record<string, unknown> = {};
|
|
917
1090
|
Object.assign(u, ownVariants);
|
|
918
1091
|
updatedVariants = u;
|
|
@@ -924,7 +1097,9 @@ export function create({
|
|
|
924
1097
|
protect = false,
|
|
925
1098
|
) => {
|
|
926
1099
|
if (shouldCollectChangedVariants) {
|
|
927
|
-
if (!changedVariants)
|
|
1100
|
+
if (!changedVariants) {
|
|
1101
|
+
changedVariants = {};
|
|
1102
|
+
}
|
|
928
1103
|
changedVariants[key] = value;
|
|
929
1104
|
}
|
|
930
1105
|
if (protect && protectedVariants) {
|
|
@@ -999,16 +1174,22 @@ export function create({
|
|
|
999
1174
|
},
|
|
1000
1175
|
addStyle: (newStyle: StyleValue) => {
|
|
1001
1176
|
if (!collectOutput) return;
|
|
1002
|
-
if (!localCStyle)
|
|
1177
|
+
if (!localCStyle) {
|
|
1178
|
+
localCStyle = {};
|
|
1179
|
+
}
|
|
1003
1180
|
Object.assign(localCStyle, newStyle);
|
|
1004
1181
|
},
|
|
1005
1182
|
};
|
|
1006
|
-
const result =
|
|
1183
|
+
const result = refine(ctx);
|
|
1007
1184
|
if (collectOutput && result != null) {
|
|
1008
1185
|
const r = extractClassAndStylePrebuilt(result);
|
|
1009
|
-
if (r.class != null)
|
|
1186
|
+
if (r.class != null) {
|
|
1187
|
+
localCClasses?.push(r.class);
|
|
1188
|
+
}
|
|
1010
1189
|
if (r.style) {
|
|
1011
|
-
if (!localCStyle)
|
|
1190
|
+
if (!localCStyle) {
|
|
1191
|
+
localCStyle = {};
|
|
1192
|
+
}
|
|
1012
1193
|
Object.assign(localCStyle, r.style);
|
|
1013
1194
|
}
|
|
1014
1195
|
}
|
|
@@ -1052,14 +1233,14 @@ export function create({
|
|
|
1052
1233
|
pendingProtectedVariants,
|
|
1053
1234
|
protectedVariantKeys,
|
|
1054
1235
|
) => {
|
|
1055
|
-
// Run `
|
|
1236
|
+
// Run `refine` (if any). May modify resolved variants and emit classes
|
|
1056
1237
|
// and styles.
|
|
1057
1238
|
let workingResolved = resolved;
|
|
1058
1239
|
let cClasses: ClassValue[] | null = null;
|
|
1059
1240
|
let cStyle: StyleValue | null = null;
|
|
1060
1241
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1061
|
-
if (
|
|
1062
|
-
const
|
|
1242
|
+
if (refine) {
|
|
1243
|
+
const refineResult = runRefineContext(
|
|
1063
1244
|
resolved,
|
|
1064
1245
|
userVariantProps,
|
|
1065
1246
|
true,
|
|
@@ -1068,20 +1249,20 @@ export function create({
|
|
|
1068
1249
|
pendingProtectedVariants,
|
|
1069
1250
|
protectedVariantKeys,
|
|
1070
1251
|
);
|
|
1071
|
-
workingResolved =
|
|
1072
|
-
cClasses =
|
|
1073
|
-
cStyle =
|
|
1074
|
-
changedVariants =
|
|
1252
|
+
workingResolved = refineResult.workingResolved;
|
|
1253
|
+
cClasses = refineResult.classes;
|
|
1254
|
+
cStyle = refineResult.style;
|
|
1255
|
+
changedVariants = refineResult.changedVariants;
|
|
1075
1256
|
}
|
|
1076
1257
|
|
|
1077
1258
|
// Run extends' contributions first (their full classes + styles) so our
|
|
1078
1259
|
// own base style and variants apply on top, matching the original
|
|
1079
1260
|
// ext1 → ext2 → … → current ordering.
|
|
1080
1261
|
//
|
|
1081
|
-
// Pass explicit user values plus
|
|
1082
|
-
// `userVariantProps`. This lets more-specific
|
|
1262
|
+
// Pass explicit user values plus refine changes as the extends'
|
|
1263
|
+
// `userVariantProps`. This lets more-specific refine decisions stick
|
|
1083
1264
|
// across re-runs while inherited static defaults can still be refined by
|
|
1084
|
-
// the extended component's own
|
|
1265
|
+
// the extended component's own refine chain.
|
|
1085
1266
|
if (hasExtend) {
|
|
1086
1267
|
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
1087
1268
|
// caller-provided sets need merging.
|
|
@@ -1092,7 +1273,9 @@ export function create({
|
|
|
1092
1273
|
extSkipKeys = skipKeys;
|
|
1093
1274
|
} else {
|
|
1094
1275
|
extSkipKeys = new Set(skipKeys);
|
|
1095
|
-
for (const k of staticExtSkipKeys)
|
|
1276
|
+
for (const k of staticExtSkipKeys) {
|
|
1277
|
+
extSkipKeys.add(k);
|
|
1278
|
+
}
|
|
1096
1279
|
}
|
|
1097
1280
|
|
|
1098
1281
|
let extSkipVals: Record<string, Set<string>> | null;
|
|
@@ -1109,7 +1292,9 @@ export function create({
|
|
|
1109
1292
|
const existing = extSkipVals[k];
|
|
1110
1293
|
if (existing) {
|
|
1111
1294
|
const merged = new Set<string>(existing);
|
|
1112
|
-
for (const v of staticExtSkipValues[k])
|
|
1295
|
+
for (const v of staticExtSkipValues[k]) {
|
|
1296
|
+
merged.add(v);
|
|
1297
|
+
}
|
|
1113
1298
|
extSkipVals[k] = merged;
|
|
1114
1299
|
} else {
|
|
1115
1300
|
extSkipVals[k] = staticExtSkipValues[k];
|
|
@@ -1118,7 +1303,7 @@ export function create({
|
|
|
1118
1303
|
}
|
|
1119
1304
|
|
|
1120
1305
|
const extUserVariantProps =
|
|
1121
|
-
|
|
1306
|
+
extMetasWithRefineCount > 0
|
|
1122
1307
|
? getExtUserVariantProps(
|
|
1123
1308
|
userVariantProps,
|
|
1124
1309
|
protectedVariants ?? null,
|
|
@@ -1165,21 +1350,24 @@ export function create({
|
|
|
1165
1350
|
protectedVariantKeys,
|
|
1166
1351
|
);
|
|
1167
1352
|
}
|
|
1168
|
-
// Only sync protected variants when a child
|
|
1353
|
+
// Only sync protected variants when a child refine resolver can
|
|
1169
1354
|
// observe them. Otherwise extUserVariantProps may alias caller props.
|
|
1170
|
-
if (protectedVariants &&
|
|
1355
|
+
if (protectedVariants && extMetasWithRefineCount > 0) {
|
|
1171
1356
|
Object.assign(extUserVariantProps, protectedVariants);
|
|
1172
1357
|
}
|
|
1173
1358
|
}
|
|
1174
1359
|
}
|
|
1175
1360
|
|
|
1176
1361
|
// Apply own base style (after extends' styles, matching original order).
|
|
1177
|
-
if (hasBaseStyle)
|
|
1362
|
+
if (hasBaseStyle) {
|
|
1363
|
+
Object.assign(styleOut, baseStyle);
|
|
1364
|
+
}
|
|
1178
1365
|
|
|
1179
1366
|
// Apply own variants. Skip keys/values come from caller (e.g., parent
|
|
1180
|
-
// wants its own
|
|
1367
|
+
// wants its own function variant to override this variant).
|
|
1181
1368
|
// `variantEntryNames` already excludes disabled keys (those with `null`
|
|
1182
|
-
// value in config), so we don't re-check
|
|
1369
|
+
// value in config) and function variants, so we don't re-check
|
|
1370
|
+
// `disabledVariantKeys` here.
|
|
1183
1371
|
const ownSkipKeys = skipKeys;
|
|
1184
1372
|
const ownSkipValues = skipValues;
|
|
1185
1373
|
for (let i = 0; i < variantEntryCount; i++) {
|
|
@@ -1208,18 +1396,28 @@ export function create({
|
|
|
1208
1396
|
if (selectedKey == null) continue;
|
|
1209
1397
|
const v = variant.values[selectedKey];
|
|
1210
1398
|
if (!v) continue;
|
|
1211
|
-
if (v.class != null)
|
|
1212
|
-
|
|
1399
|
+
if (v.class != null) {
|
|
1400
|
+
classesOut.push(v.class as ClsxClassValue);
|
|
1401
|
+
}
|
|
1402
|
+
if (v.style) {
|
|
1403
|
+
Object.assign(styleOut, v.style);
|
|
1404
|
+
}
|
|
1213
1405
|
} else if (variant.shorthand && selectedValue === true) {
|
|
1214
1406
|
const v = variant.shorthand;
|
|
1215
|
-
if (v.class != null)
|
|
1216
|
-
|
|
1407
|
+
if (v.class != null) {
|
|
1408
|
+
classesOut.push(v.class as ClsxClassValue);
|
|
1409
|
+
}
|
|
1410
|
+
if (v.style) {
|
|
1411
|
+
Object.assign(styleOut, v.style);
|
|
1412
|
+
}
|
|
1217
1413
|
}
|
|
1218
1414
|
}
|
|
1219
1415
|
|
|
1220
|
-
// Apply
|
|
1221
|
-
|
|
1222
|
-
|
|
1416
|
+
// Apply function variants — entries in `variants` whose value is a
|
|
1417
|
+
// function. They run after static variants and override any same-key
|
|
1418
|
+
// inherited variant via `staticExtSkipKeys`.
|
|
1419
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
1420
|
+
const variantName = functionVariantNames[i];
|
|
1223
1421
|
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
1224
1422
|
const selectedValue = workingResolved[variantName];
|
|
1225
1423
|
if (selectedValue === undefined) continue;
|
|
@@ -1231,52 +1429,35 @@ export function create({
|
|
|
1231
1429
|
) {
|
|
1232
1430
|
continue;
|
|
1233
1431
|
}
|
|
1234
|
-
const fn =
|
|
1432
|
+
const fn = functionVariantFns[i];
|
|
1235
1433
|
const computedResult = fn(selectedValue);
|
|
1236
1434
|
if (computedResult == null) continue;
|
|
1237
1435
|
const r = extractClassAndStylePrebuilt(computedResult);
|
|
1238
|
-
if (r.class != null)
|
|
1239
|
-
|
|
1436
|
+
if (r.class != null) {
|
|
1437
|
+
classesOut.push(r.class as ClsxClassValue);
|
|
1438
|
+
}
|
|
1439
|
+
if (r.style) {
|
|
1440
|
+
Object.assign(styleOut, r.style);
|
|
1441
|
+
}
|
|
1240
1442
|
}
|
|
1241
1443
|
|
|
1242
|
-
// Apply `
|
|
1444
|
+
// Apply `refine` results — must come after own variants (static and
|
|
1445
|
+
// function).
|
|
1243
1446
|
if (cClasses) {
|
|
1244
1447
|
for (let i = 0; i < cClasses.length; i++) {
|
|
1245
1448
|
classesOut.push(cClasses[i] as ClsxClassValue);
|
|
1246
1449
|
}
|
|
1247
1450
|
}
|
|
1248
|
-
if (cStyle)
|
|
1451
|
+
if (cStyle) {
|
|
1452
|
+
Object.assign(styleOut, cStyle);
|
|
1453
|
+
}
|
|
1249
1454
|
|
|
1250
1455
|
return workingResolved;
|
|
1251
1456
|
};
|
|
1252
1457
|
|
|
1253
1458
|
const compute: ComputeFn =
|
|
1254
|
-
!
|
|
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
|
-
}
|
|
1459
|
+
!refine && extMetasWithRefineCount === 0
|
|
1460
|
+
? computeOnce
|
|
1280
1461
|
: (
|
|
1281
1462
|
resolved,
|
|
1282
1463
|
userVariantProps,
|
|
@@ -1289,10 +1470,15 @@ export function create({
|
|
|
1289
1470
|
pendingProtectedVariants,
|
|
1290
1471
|
protectedVariantKeys,
|
|
1291
1472
|
) => {
|
|
1292
|
-
runState ??= { remaining:
|
|
1473
|
+
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
1293
1474
|
protectedVariants ??= {};
|
|
1294
1475
|
protectedVariantKeys ??= new Set<string>();
|
|
1295
1476
|
let workingResolved = resolved;
|
|
1477
|
+
// Union of variant keys that differed on non-converging iterations
|
|
1478
|
+
// inside the tracking window, so the refine-limit warning can name
|
|
1479
|
+
// every variant that contributed to the late-stage oscillation.
|
|
1480
|
+
// Lazy-init keeps convergent loops allocation-free.
|
|
1481
|
+
let unstableKeys: Set<string> | null = null;
|
|
1296
1482
|
let lastClasses: ClsxClassValue[] = [];
|
|
1297
1483
|
let lastStyle: StyleValue = {};
|
|
1298
1484
|
let isFirstRun = true;
|
|
@@ -1356,10 +1542,24 @@ export function create({
|
|
|
1356
1542
|
return nextResolved;
|
|
1357
1543
|
}
|
|
1358
1544
|
|
|
1545
|
+
if (
|
|
1546
|
+
process.env.NODE_ENV !== "production" &&
|
|
1547
|
+
runState.remaining < REFINE_UNSTABLE_TRACKING_WINDOW
|
|
1548
|
+
) {
|
|
1549
|
+
if (!unstableKeys) {
|
|
1550
|
+
unstableKeys = new Set<string>();
|
|
1551
|
+
}
|
|
1552
|
+
accumulateUnstableVariantKeys(
|
|
1553
|
+
unstableKeys,
|
|
1554
|
+
workingResolved,
|
|
1555
|
+
nextResolved,
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1359
1559
|
if (useDirectOutput && runState.remaining === 0) {
|
|
1360
1560
|
// Keep the direct output from the last allowed run. Rolling
|
|
1361
1561
|
// back here would drop it before the fallback copy below.
|
|
1362
|
-
|
|
1562
|
+
warnRefineLimit(runState, creationFrame, unstableKeys);
|
|
1363
1563
|
return nextResolved;
|
|
1364
1564
|
}
|
|
1365
1565
|
|
|
@@ -1379,7 +1579,7 @@ export function create({
|
|
|
1379
1579
|
isFirstRun = false;
|
|
1380
1580
|
}
|
|
1381
1581
|
|
|
1382
|
-
|
|
1582
|
+
warnRefineLimit(runState, creationFrame, unstableKeys);
|
|
1383
1583
|
|
|
1384
1584
|
for (let i = 0; i < lastClasses.length; i++) {
|
|
1385
1585
|
classesOut.push(lastClasses[i]);
|
|
@@ -1388,7 +1588,7 @@ export function create({
|
|
|
1388
1588
|
return workingResolved;
|
|
1389
1589
|
};
|
|
1390
1590
|
|
|
1391
|
-
const
|
|
1591
|
+
const resolveRefineOnce: ResolveRefineFn = (
|
|
1392
1592
|
resolved,
|
|
1393
1593
|
userVariantProps,
|
|
1394
1594
|
filterOwnVariants = true,
|
|
@@ -1399,8 +1599,8 @@ export function create({
|
|
|
1399
1599
|
) => {
|
|
1400
1600
|
let workingResolved = resolved;
|
|
1401
1601
|
let changedVariants: Record<string, unknown> | null = null;
|
|
1402
|
-
if (
|
|
1403
|
-
const
|
|
1602
|
+
if (refine) {
|
|
1603
|
+
const refineResult = runRefineContext(
|
|
1404
1604
|
resolved,
|
|
1405
1605
|
userVariantProps,
|
|
1406
1606
|
filterOwnVariants,
|
|
@@ -1409,21 +1609,21 @@ export function create({
|
|
|
1409
1609
|
pendingProtectedVariants,
|
|
1410
1610
|
protectedVariantKeys,
|
|
1411
1611
|
);
|
|
1412
|
-
workingResolved =
|
|
1413
|
-
changedVariants =
|
|
1612
|
+
workingResolved = refineResult.workingResolved;
|
|
1613
|
+
changedVariants = refineResult.changedVariants;
|
|
1414
1614
|
}
|
|
1415
1615
|
|
|
1416
|
-
if (
|
|
1616
|
+
if (extMetasWithRefineCount > 0) {
|
|
1417
1617
|
const extUserVariantProps = getExtUserVariantProps(
|
|
1418
1618
|
userVariantProps,
|
|
1419
1619
|
protectedVariants ?? null,
|
|
1420
1620
|
changedVariants,
|
|
1421
1621
|
);
|
|
1422
|
-
for (let i = 0; i <
|
|
1423
|
-
const meta =
|
|
1424
|
-
const
|
|
1425
|
-
if (!
|
|
1426
|
-
workingResolved =
|
|
1622
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1623
|
+
const meta = extMetasWithRefine[i];
|
|
1624
|
+
const resolveRefine = meta.resolveRefine;
|
|
1625
|
+
if (!resolveRefine) continue;
|
|
1626
|
+
workingResolved = resolveRefine(
|
|
1427
1627
|
workingResolved,
|
|
1428
1628
|
extUserVariantProps,
|
|
1429
1629
|
true,
|
|
@@ -1441,8 +1641,8 @@ export function create({
|
|
|
1441
1641
|
return workingResolved;
|
|
1442
1642
|
};
|
|
1443
1643
|
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1644
|
+
const resolveRefine: ResolveRefineFn | null =
|
|
1645
|
+
refine || extMetasWithRefineCount > 0
|
|
1446
1646
|
? (
|
|
1447
1647
|
resolved,
|
|
1448
1648
|
userVariantProps,
|
|
@@ -1452,16 +1652,21 @@ export function create({
|
|
|
1452
1652
|
pendingProtectedVariants,
|
|
1453
1653
|
protectedVariantKeys,
|
|
1454
1654
|
) => {
|
|
1455
|
-
runState ??= { remaining:
|
|
1655
|
+
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
1456
1656
|
protectedVariants ??= {};
|
|
1457
1657
|
protectedVariantKeys ??= new Set<string>();
|
|
1458
1658
|
let workingResolved = resolved;
|
|
1659
|
+
// Union of variant keys that differed on non-converging iterations
|
|
1660
|
+
// inside the tracking window — see the compute loop above for the
|
|
1661
|
+
// shared rationale. Lazy-init keeps convergent loops
|
|
1662
|
+
// allocation-free.
|
|
1663
|
+
let unstableKeys: Set<string> | null = null;
|
|
1459
1664
|
let reachedLimit = true;
|
|
1460
1665
|
|
|
1461
1666
|
while (runState.remaining > 0) {
|
|
1462
1667
|
runState.remaining -= 1;
|
|
1463
1668
|
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1464
|
-
const nextResolved =
|
|
1669
|
+
const nextResolved = resolveRefineOnce(
|
|
1465
1670
|
workingResolved,
|
|
1466
1671
|
userVariantProps,
|
|
1467
1672
|
filterOwnVariants,
|
|
@@ -1495,11 +1700,24 @@ export function create({
|
|
|
1495
1700
|
break;
|
|
1496
1701
|
}
|
|
1497
1702
|
|
|
1703
|
+
if (
|
|
1704
|
+
process.env.NODE_ENV !== "production" &&
|
|
1705
|
+
runState.remaining < REFINE_UNSTABLE_TRACKING_WINDOW
|
|
1706
|
+
) {
|
|
1707
|
+
if (!unstableKeys) {
|
|
1708
|
+
unstableKeys = new Set<string>();
|
|
1709
|
+
}
|
|
1710
|
+
accumulateUnstableVariantKeys(
|
|
1711
|
+
unstableKeys,
|
|
1712
|
+
workingResolved,
|
|
1713
|
+
nextResolved,
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1498
1716
|
workingResolved = nextResolved;
|
|
1499
1717
|
}
|
|
1500
1718
|
|
|
1501
1719
|
if (reachedLimit) {
|
|
1502
|
-
|
|
1720
|
+
warnRefineLimit(runState, creationFrame, unstableKeys);
|
|
1503
1721
|
}
|
|
1504
1722
|
|
|
1505
1723
|
return workingResolved;
|
|
@@ -1521,7 +1739,7 @@ export function create({
|
|
|
1521
1739
|
Object.assign(resolved, staticDefaults);
|
|
1522
1740
|
|
|
1523
1741
|
let userVariantProps: Record<string, unknown>;
|
|
1524
|
-
if (
|
|
1742
|
+
if (extMetasWithRefineCount > 0) {
|
|
1525
1743
|
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1526
1744
|
// object as `userProps`, so we extract one.
|
|
1527
1745
|
const variantProps: Record<string, unknown> = {};
|
|
@@ -1531,12 +1749,12 @@ export function create({
|
|
|
1531
1749
|
variantProps[key] = propsRecord[key];
|
|
1532
1750
|
}
|
|
1533
1751
|
}
|
|
1534
|
-
for (let i = 0; i <
|
|
1535
|
-
const meta =
|
|
1536
|
-
const
|
|
1537
|
-
for (const k in
|
|
1538
|
-
if (!Object.hasOwn(
|
|
1539
|
-
resolved[k] =
|
|
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];
|
|
1540
1758
|
}
|
|
1541
1759
|
}
|
|
1542
1760
|
for (const k in variantProps) {
|
|
@@ -1610,12 +1828,8 @@ export function create({
|
|
|
1610
1828
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1611
1829
|
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1612
1830
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1613
|
-
if (
|
|
1614
|
-
resolvedVariants =
|
|
1615
|
-
resolvedVariants,
|
|
1616
|
-
variantProps,
|
|
1617
|
-
false,
|
|
1618
|
-
);
|
|
1831
|
+
if (resolveRefine) {
|
|
1832
|
+
resolvedVariants = resolveRefine(resolvedVariants, variantProps, false);
|
|
1619
1833
|
}
|
|
1620
1834
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1621
1835
|
};
|
|
@@ -1642,8 +1856,9 @@ export function create({
|
|
|
1642
1856
|
staticDefaults,
|
|
1643
1857
|
resolveDefaults: resolveDefaultsFn,
|
|
1644
1858
|
compute,
|
|
1645
|
-
|
|
1859
|
+
resolveRefine,
|
|
1646
1860
|
transformClass,
|
|
1861
|
+
functionVariantKeys,
|
|
1647
1862
|
};
|
|
1648
1863
|
|
|
1649
1864
|
const initComponent = <
|
|
@@ -1667,7 +1882,7 @@ export function create({
|
|
|
1667
1882
|
const defaultComponent = ((props: ComponentProps<MergedVariants> = {}) => {
|
|
1668
1883
|
const { className, style } = computeResult(props);
|
|
1669
1884
|
return { class: className, style };
|
|
1670
|
-
}) as CVComponent<V,
|
|
1885
|
+
}) as CVComponent<V, E>;
|
|
1671
1886
|
initComponent(defaultComponent, inputPropsKeys, (props = {}) => {
|
|
1672
1887
|
return computeResult(props).style;
|
|
1673
1888
|
});
|