clava 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +552 -0
- package/dist/index.d.ts +22 -30
- package/dist/index.js +356 -171
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rolldown.config.ts +1 -0
- package/src/index.ts +718 -285
- package/src/types.ts +44 -49
- package/tests/_utils.ts +1 -3
- package/tests/build.test.ts +18 -0
- package/tests/component-api.test.ts +284 -15
- package/tests/extend.test.ts +6 -6
- package/tests/{computed-variants.test.ts → function-variants.test.ts} +105 -25
- package/tests/prototype-pollution.test.ts +6 -6
- package/tests/{computed.test.ts → refine.test.ts} +517 -100
- package/tests/solid.test.ts +30 -0
- package/tests/split-props.test.ts +73 -1
- 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,14 +41,33 @@ type ComputeFn = (
|
|
|
42
41
|
skipValues: Record<string, Set<string>> | null,
|
|
43
42
|
classesOut: ClsxClassValue[],
|
|
44
43
|
styleOut: StyleValue,
|
|
45
|
-
|
|
44
|
+
runState?: RefineRunState,
|
|
45
|
+
protectedVariants?: Record<string, unknown> | null,
|
|
46
|
+
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
47
|
+
protectedVariantKeys?: Set<string> | null,
|
|
48
|
+
) => Record<string, unknown>;
|
|
49
|
+
|
|
50
|
+
type ResolveRefineFn = (
|
|
51
|
+
resolved: Record<string, unknown>,
|
|
52
|
+
userVariantProps: Record<string, unknown>,
|
|
53
|
+
filterOwnVariants?: boolean,
|
|
54
|
+
runState?: RefineRunState,
|
|
55
|
+
protectedVariants?: Record<string, unknown> | null,
|
|
56
|
+
pendingProtectedVariants?: Record<string, unknown> | null,
|
|
57
|
+
protectedVariantKeys?: Set<string> | null,
|
|
58
|
+
) => Record<string, unknown>;
|
|
59
|
+
|
|
60
|
+
interface RefineRunState {
|
|
61
|
+
remaining: number;
|
|
62
|
+
warned: boolean;
|
|
63
|
+
}
|
|
46
64
|
|
|
47
65
|
// Internal metadata stored on components but hidden from public types.
|
|
48
66
|
interface ComponentMeta {
|
|
49
67
|
baseClass: string;
|
|
50
68
|
staticDefaults: Record<string, unknown>;
|
|
51
|
-
// Returns variants set via setDefaultVariants in the
|
|
52
|
-
// 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`
|
|
53
71
|
// and no extends with work).
|
|
54
72
|
resolveDefaults:
|
|
55
73
|
| ((
|
|
@@ -60,23 +78,97 @@ interface ComponentMeta {
|
|
|
60
78
|
// Returns variant classes + style for this component, used by extending
|
|
61
79
|
// components. Top-level rendering also routes through this.
|
|
62
80
|
compute: ComputeFn;
|
|
81
|
+
resolveRefine: ResolveRefineFn | null;
|
|
63
82
|
// Reference identity is used to detect mixed-factory `extend`. When a
|
|
64
83
|
// component is extended by a parent from a different `create()` call, the
|
|
65
84
|
// parent applies this transform to the extend's contribution before joining,
|
|
66
85
|
// preserving each factory's transform boundary.
|
|
67
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>;
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
const META_KEY = "__meta";
|
|
71
96
|
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
73
|
-
const hasOwn = Object.prototype.hasOwnProperty;
|
|
74
|
-
|
|
75
97
|
const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
76
98
|
string,
|
|
77
99
|
unknown
|
|
78
100
|
>;
|
|
79
101
|
|
|
102
|
+
const MAX_REFINE_RUNS = 50;
|
|
103
|
+
|
|
104
|
+
function areVariantsEqual(
|
|
105
|
+
a: Record<string, unknown>,
|
|
106
|
+
b: Record<string, unknown>,
|
|
107
|
+
): boolean {
|
|
108
|
+
for (const key in a) {
|
|
109
|
+
if (!Object.hasOwn(a, key)) continue;
|
|
110
|
+
if (!Object.is(a[key], b[key])) return false;
|
|
111
|
+
}
|
|
112
|
+
for (const key in b) {
|
|
113
|
+
if (!Object.hasOwn(b, key)) continue;
|
|
114
|
+
if (!Object.hasOwn(a, key)) return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function warnRefineLimit(runState: RefineRunState): void {
|
|
120
|
+
if (runState.warned) return;
|
|
121
|
+
runState.warned = true;
|
|
122
|
+
if (process.env.NODE_ENV !== "production") {
|
|
123
|
+
console.warn(
|
|
124
|
+
"Clava: Maximum refine iterations exceeded. This can happen when a " +
|
|
125
|
+
"refine callback calls setVariants or setDefaultVariants, but one " +
|
|
126
|
+
"of the variants changes on every run.",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getExtUserVariantProps(
|
|
132
|
+
userVariantProps: Record<string, unknown>,
|
|
133
|
+
protectedVariants: Record<string, unknown> | null,
|
|
134
|
+
changedVariants: Record<string, unknown> | null,
|
|
135
|
+
): Record<string, unknown> {
|
|
136
|
+
const extUserVariantProps: Record<string, unknown> = {};
|
|
137
|
+
Object.assign(extUserVariantProps, userVariantProps);
|
|
138
|
+
if (protectedVariants) {
|
|
139
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
140
|
+
}
|
|
141
|
+
if (changedVariants) {
|
|
142
|
+
Object.assign(extUserVariantProps, changedVariants);
|
|
143
|
+
}
|
|
144
|
+
return extUserVariantProps;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function mergeVariants(
|
|
148
|
+
target: Record<string, unknown>,
|
|
149
|
+
source: Record<string, unknown>,
|
|
150
|
+
skipKeys?: Set<string> | null,
|
|
151
|
+
): boolean {
|
|
152
|
+
let changed = false;
|
|
153
|
+
if (!skipKeys || skipKeys.size === 0) {
|
|
154
|
+
for (const key in source) {
|
|
155
|
+
if (!Object.hasOwn(source, key)) continue;
|
|
156
|
+
const value = source[key];
|
|
157
|
+
if (!Object.is(target[key], value)) changed = true;
|
|
158
|
+
target[key] = value;
|
|
159
|
+
}
|
|
160
|
+
return changed;
|
|
161
|
+
}
|
|
162
|
+
for (const key in source) {
|
|
163
|
+
if (!Object.hasOwn(source, key)) continue;
|
|
164
|
+
if (skipKeys.has(key)) continue;
|
|
165
|
+
const value = source[key];
|
|
166
|
+
if (!Object.is(target[key], value)) changed = true;
|
|
167
|
+
target[key] = value;
|
|
168
|
+
}
|
|
169
|
+
return changed;
|
|
170
|
+
}
|
|
171
|
+
|
|
80
172
|
// Dynamic property access on function requires cast through unknown.
|
|
81
173
|
function getComponentMeta(component: AnyComponent): ComponentMeta | undefined {
|
|
82
174
|
return (component as unknown as Record<string, unknown>)[META_KEY] as
|
|
@@ -115,16 +207,14 @@ export type Variant<
|
|
|
115
207
|
|
|
116
208
|
export interface CVConfig<
|
|
117
209
|
V extends Variants = {},
|
|
118
|
-
CV extends ComputedVariants = {},
|
|
119
210
|
E extends AnyComponent[] = [],
|
|
120
211
|
> {
|
|
121
212
|
extend?: E;
|
|
122
213
|
class?: ClassValue;
|
|
123
214
|
style?: StyleValue;
|
|
124
215
|
variants?: ExtendableVariants<V, E>;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
computed?: Computed<MergeVariants<V, CV, E>>;
|
|
216
|
+
defaultVariants?: VariantValues<MergeVariants<V, E>>;
|
|
217
|
+
refine?: Refine<MergeVariants<V, E>>;
|
|
128
218
|
}
|
|
129
219
|
|
|
130
220
|
interface CreateParams {
|
|
@@ -195,7 +285,7 @@ function extractClassAndStylePrebuilt(value: unknown): PrebuiltValue {
|
|
|
195
285
|
* components.
|
|
196
286
|
*/
|
|
197
287
|
function collectVariantKeys(
|
|
198
|
-
config: CVConfig<Variants,
|
|
288
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
199
289
|
): string[] {
|
|
200
290
|
const keys = new Set<string>();
|
|
201
291
|
|
|
@@ -210,7 +300,7 @@ function collectVariantKeys(
|
|
|
210
300
|
|
|
211
301
|
if (config.variants) {
|
|
212
302
|
for (const key in config.variants) {
|
|
213
|
-
if (!hasOwn
|
|
303
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
214
304
|
const variant = (config.variants as Record<string, unknown>)[key];
|
|
215
305
|
if (variant === null) {
|
|
216
306
|
keys.delete(key);
|
|
@@ -220,18 +310,11 @@ function collectVariantKeys(
|
|
|
220
310
|
}
|
|
221
311
|
}
|
|
222
312
|
|
|
223
|
-
if (config.computedVariants) {
|
|
224
|
-
for (const key in config.computedVariants) {
|
|
225
|
-
if (!hasOwn.call(config.computedVariants, key)) continue;
|
|
226
|
-
keys.add(key);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
313
|
return Array.from(keys);
|
|
231
314
|
}
|
|
232
315
|
|
|
233
316
|
function isVariantDisabled(
|
|
234
|
-
config: CVConfig<Variants,
|
|
317
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
235
318
|
key: string,
|
|
236
319
|
): boolean {
|
|
237
320
|
return config.variants?.[key] === null;
|
|
@@ -245,7 +328,7 @@ function getVariantValueKey(value: unknown): string | undefined {
|
|
|
245
328
|
}
|
|
246
329
|
|
|
247
330
|
function isVariantValueDisabled(
|
|
248
|
-
config: CVConfig<Variants,
|
|
331
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
249
332
|
key: string,
|
|
250
333
|
value: unknown,
|
|
251
334
|
): boolean {
|
|
@@ -257,12 +340,12 @@ function isVariantValueDisabled(
|
|
|
257
340
|
}
|
|
258
341
|
|
|
259
342
|
function collectDisabledVariantKeys(
|
|
260
|
-
config: CVConfig<Variants,
|
|
343
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
261
344
|
): Set<string> {
|
|
262
345
|
const keys = new Set<string>();
|
|
263
346
|
if (!config.variants) return keys;
|
|
264
347
|
for (const key in config.variants) {
|
|
265
|
-
if (!hasOwn
|
|
348
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
266
349
|
if ((config.variants as Record<string, unknown>)[key] === null) {
|
|
267
350
|
keys.add(key);
|
|
268
351
|
}
|
|
@@ -271,17 +354,17 @@ function collectDisabledVariantKeys(
|
|
|
271
354
|
}
|
|
272
355
|
|
|
273
356
|
function collectDisabledVariantValues(
|
|
274
|
-
config: CVConfig<Variants,
|
|
357
|
+
config: CVConfig<Variants, AnyComponent[]>,
|
|
275
358
|
): Record<string, Set<string>> {
|
|
276
359
|
const values: Record<string, Set<string>> = {};
|
|
277
360
|
if (!config.variants) return values;
|
|
278
361
|
for (const key in config.variants) {
|
|
279
|
-
if (!hasOwn
|
|
362
|
+
if (!Object.hasOwn(config.variants, key)) continue;
|
|
280
363
|
const variant = (config.variants as Record<string, unknown>)[key];
|
|
281
364
|
if (!isRecordObject(variant)) continue;
|
|
282
365
|
let bucket: Set<string> | undefined;
|
|
283
366
|
for (const variantValue in variant) {
|
|
284
|
-
if (!hasOwn
|
|
367
|
+
if (!Object.hasOwn(variant, variantValue)) continue;
|
|
285
368
|
if (variant[variantValue] !== null) continue;
|
|
286
369
|
if (!bucket) {
|
|
287
370
|
bucket = new Set<string>();
|
|
@@ -294,13 +377,13 @@ function collectDisabledVariantValues(
|
|
|
294
377
|
}
|
|
295
378
|
|
|
296
379
|
interface NormalizedSource {
|
|
297
|
-
|
|
380
|
+
propKeys: string[];
|
|
298
381
|
variantKeys: string[];
|
|
299
382
|
isComponent: boolean;
|
|
300
383
|
}
|
|
301
384
|
|
|
302
385
|
const EMPTY_SOURCE: NormalizedSource = {
|
|
303
|
-
|
|
386
|
+
propKeys: [],
|
|
304
387
|
variantKeys: [],
|
|
305
388
|
isComponent: false,
|
|
306
389
|
};
|
|
@@ -308,7 +391,7 @@ const EMPTY_SOURCE: NormalizedSource = {
|
|
|
308
391
|
function normalizeKeySource(source: unknown): NormalizedSource {
|
|
309
392
|
if (Array.isArray(source)) {
|
|
310
393
|
return {
|
|
311
|
-
|
|
394
|
+
propKeys: source as string[],
|
|
312
395
|
variantKeys: source as string[],
|
|
313
396
|
isComponent: false,
|
|
314
397
|
};
|
|
@@ -318,17 +401,14 @@ function normalizeKeySource(source: unknown): NormalizedSource {
|
|
|
318
401
|
if (typeof source !== "object" && typeof source !== "function") {
|
|
319
402
|
return EMPTY_SOURCE;
|
|
320
403
|
}
|
|
321
|
-
|
|
322
|
-
if (
|
|
404
|
+
const typed = source as Record<string, unknown>;
|
|
405
|
+
if (typeof typed.getVariants !== "function") return EMPTY_SOURCE;
|
|
406
|
+
if (!Array.isArray(typed.propKeys)) return EMPTY_SOURCE;
|
|
407
|
+
if (!Array.isArray(typed.variantKeys)) return EMPTY_SOURCE;
|
|
323
408
|
|
|
324
|
-
// Component-provided arrays are immutable metadata — reference directly.
|
|
325
|
-
const typed = source as {
|
|
326
|
-
keys: string[];
|
|
327
|
-
variantKeys: string[];
|
|
328
|
-
};
|
|
329
409
|
return {
|
|
330
|
-
|
|
331
|
-
variantKeys: typed.variantKeys,
|
|
410
|
+
propKeys: typed.propKeys as string[],
|
|
411
|
+
variantKeys: typed.variantKeys as string[],
|
|
332
412
|
isComponent: true,
|
|
333
413
|
};
|
|
334
414
|
}
|
|
@@ -368,7 +448,9 @@ function splitPropsImpl(
|
|
|
368
448
|
const sourceResult: Record<string, unknown> = {};
|
|
369
449
|
|
|
370
450
|
const effectiveKeys =
|
|
371
|
-
source.isComponent && stylingClaimed
|
|
451
|
+
source.isComponent && stylingClaimed
|
|
452
|
+
? source.variantKeys
|
|
453
|
+
: source.propKeys;
|
|
372
454
|
|
|
373
455
|
const effectiveKeysLength = effectiveKeys.length;
|
|
374
456
|
for (let i = 0; i < effectiveKeysLength; i++) {
|
|
@@ -422,7 +504,7 @@ export const splitProps: SplitPropsFunction = ((
|
|
|
422
504
|
) => {
|
|
423
505
|
const normalizedSource1 = normalizeKeySource(source1);
|
|
424
506
|
return splitPropsImpl(
|
|
425
|
-
normalizedSource1.
|
|
507
|
+
normalizedSource1.propKeys,
|
|
426
508
|
normalizedSource1.isComponent,
|
|
427
509
|
props,
|
|
428
510
|
sources,
|
|
@@ -455,7 +537,7 @@ function buildPrebuiltVariant(variantDef: unknown): PrebuiltVariant {
|
|
|
455
537
|
const values: Record<string, PrebuiltValue> = {};
|
|
456
538
|
let disabledValues: Set<string> | null = null;
|
|
457
539
|
for (const key in variantDef) {
|
|
458
|
-
if (!hasOwn
|
|
540
|
+
if (!Object.hasOwn(variantDef, key)) continue;
|
|
459
541
|
const value = variantDef[key];
|
|
460
542
|
if (value === null) {
|
|
461
543
|
if (!disabledValues) disabledValues = new Set<string>();
|
|
@@ -479,14 +561,10 @@ export function create({
|
|
|
479
561
|
}: CreateParams = {}) {
|
|
480
562
|
const cx = (...classes: ClsxClassValue[]) => transformClass(clsx(...classes));
|
|
481
563
|
|
|
482
|
-
const cv = <
|
|
483
|
-
V
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
>(
|
|
487
|
-
config: CVConfig<V, CV, E> = {},
|
|
488
|
-
): CVComponent<V, CV, E> => {
|
|
489
|
-
type MergedVariants = MergeVariants<V, CV, E>;
|
|
564
|
+
const cv = <V extends Variants = {}, const E extends AnyComponent[] = []>(
|
|
565
|
+
config: CVConfig<V, E> = {},
|
|
566
|
+
): CVComponent<V, E> => {
|
|
567
|
+
type MergedVariants = MergeVariants<V, E>;
|
|
490
568
|
|
|
491
569
|
// ----- Pre-computed at creation time -----
|
|
492
570
|
const variantKeys = collectVariantKeys(config);
|
|
@@ -503,41 +581,34 @@ export function create({
|
|
|
503
581
|
const extend = config.extend;
|
|
504
582
|
const hasExtend = !!extend && extend.length > 0;
|
|
505
583
|
const variants = config.variants;
|
|
506
|
-
const
|
|
507
|
-
const computed = config.computed;
|
|
584
|
+
const refine = config.refine;
|
|
508
585
|
const baseStyle = config.style;
|
|
509
586
|
const hasBaseStyle = !!baseStyle;
|
|
510
587
|
|
|
511
|
-
//
|
|
512
|
-
//
|
|
588
|
+
// Split `variants` entries into static entries (object/shorthand) and
|
|
589
|
+
// function-variant entries. Static entries are pre-built into
|
|
590
|
+
// PrebuiltVariant for fast iteration. Function-variant entries override
|
|
591
|
+
// any same-key inherited variant (see `staticExtSkipKeys`).
|
|
513
592
|
const variantEntryNames: string[] = [];
|
|
514
593
|
const variantEntryDefs: PrebuiltVariant[] = [];
|
|
594
|
+
const functionVariantNames: string[] = [];
|
|
595
|
+
const functionVariantFns: Array<(value: unknown) => unknown> = [];
|
|
515
596
|
if (variants) {
|
|
516
597
|
for (const name in variants) {
|
|
517
|
-
if (!hasOwn
|
|
598
|
+
if (!Object.hasOwn(variants, name)) continue;
|
|
518
599
|
const variant = (variants as Record<string, unknown>)[name];
|
|
519
600
|
if (variant === null) continue;
|
|
601
|
+
if (typeof variant === "function") {
|
|
602
|
+
functionVariantNames.push(name);
|
|
603
|
+
functionVariantFns.push(variant as (value: unknown) => unknown);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
520
606
|
variantEntryNames.push(name);
|
|
521
607
|
variantEntryDefs.push(buildPrebuiltVariant(variant));
|
|
522
608
|
}
|
|
523
609
|
}
|
|
524
610
|
const variantEntryCount = variantEntryNames.length;
|
|
525
|
-
|
|
526
|
-
// Pre-built computed-variants entries.
|
|
527
|
-
const computedVariantNames: string[] = [];
|
|
528
|
-
const computedVariantFns: Array<(value: unknown) => unknown> = [];
|
|
529
|
-
if (computedVariantsCfg) {
|
|
530
|
-
for (const name in computedVariantsCfg) {
|
|
531
|
-
if (!hasOwn.call(computedVariantsCfg, name)) continue;
|
|
532
|
-
computedVariantNames.push(name);
|
|
533
|
-
computedVariantFns.push(
|
|
534
|
-
(computedVariantsCfg as Record<string, (value: unknown) => unknown>)[
|
|
535
|
-
name
|
|
536
|
-
] as (value: unknown) => unknown,
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
const computedVariantCount = computedVariantNames.length;
|
|
611
|
+
const functionVariantCount = functionVariantNames.length;
|
|
541
612
|
|
|
542
613
|
// Pre-compute static defaults. Includes:
|
|
543
614
|
// - extended components' static defaults
|
|
@@ -553,11 +624,11 @@ export function create({
|
|
|
553
624
|
}
|
|
554
625
|
if (variants) {
|
|
555
626
|
for (const name in variants) {
|
|
556
|
-
if (!hasOwn
|
|
627
|
+
if (!Object.hasOwn(variants, name)) continue;
|
|
557
628
|
const variantDef = (variants as Record<string, unknown>)[name];
|
|
558
629
|
if (!isRecordObject(variantDef)) continue;
|
|
559
630
|
if (
|
|
560
|
-
hasOwn
|
|
631
|
+
Object.hasOwn(variantDef, "false") &&
|
|
561
632
|
staticDefaults[name] === undefined
|
|
562
633
|
) {
|
|
563
634
|
staticDefaults[name] = false;
|
|
@@ -570,7 +641,7 @@ export function create({
|
|
|
570
641
|
if (hasAnyDisabled) {
|
|
571
642
|
// Filter disabled variants in-place
|
|
572
643
|
for (const key in staticDefaults) {
|
|
573
|
-
if (!hasOwn
|
|
644
|
+
if (!Object.hasOwn(staticDefaults, key)) continue;
|
|
574
645
|
if (disabledVariantKeys.has(key)) {
|
|
575
646
|
delete staticDefaults[key];
|
|
576
647
|
continue;
|
|
@@ -596,7 +667,7 @@ export function create({
|
|
|
596
667
|
const extBaseClassesArr: string[] = [];
|
|
597
668
|
const extIsolated: boolean[] = [];
|
|
598
669
|
let hasIsolatedExt = false;
|
|
599
|
-
if (
|
|
670
|
+
if (hasExtend) {
|
|
600
671
|
for (const ext of extend) {
|
|
601
672
|
const meta = getComponentMeta(ext);
|
|
602
673
|
if (!meta) continue;
|
|
@@ -616,26 +687,80 @@ export function create({
|
|
|
616
687
|
}
|
|
617
688
|
const extCount = extMetas.length;
|
|
618
689
|
|
|
619
|
-
// Filter to only extends
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
const
|
|
690
|
+
// Filter to only extends with refine work in their chain. `resolveDefaults`
|
|
691
|
+
// and `resolveRefine` are populated from the same transitive condition,
|
|
692
|
+
// so one bucket is enough for both resolver paths.
|
|
693
|
+
const extMetasWithRefine: ComponentMeta[] = [];
|
|
623
694
|
for (let i = 0; i < extCount; i++) {
|
|
624
|
-
|
|
625
|
-
|
|
695
|
+
const meta = extMetas[i];
|
|
696
|
+
if (meta.resolveDefaults) {
|
|
697
|
+
extMetasWithRefine.push(meta);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const extMetasWithRefineCount = extMetasWithRefine.length;
|
|
701
|
+
const shouldCollectChangedVariants = extMetasWithRefineCount > 0;
|
|
702
|
+
|
|
703
|
+
// Function variant keys inherited from extends, filtered through this
|
|
704
|
+
// component's own variants: a static (object/shorthand) variant in this
|
|
705
|
+
// component replaces an inherited function variant for the same key.
|
|
706
|
+
// The closure is exposed on `ComponentMeta` so any further extending
|
|
707
|
+
// component can detect "ancestor's effective variant for K is a function"
|
|
708
|
+
// and skip it when overriding K with a non-function.
|
|
709
|
+
const functionVariantKeys = new Set<string>();
|
|
710
|
+
for (let i = 0; i < extCount; i++) {
|
|
711
|
+
const fnKeys = extMetas[i].functionVariantKeys;
|
|
712
|
+
for (const k of fnKeys) {
|
|
713
|
+
if (disabledVariantKeys.has(k)) continue;
|
|
714
|
+
functionVariantKeys.add(k);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
718
|
+
functionVariantKeys.add(functionVariantNames[i]);
|
|
719
|
+
}
|
|
720
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
721
|
+
// A static variant in this component replaces an inherited function
|
|
722
|
+
// variant for the same key; from this component onward, the key is no
|
|
723
|
+
// longer a function variant.
|
|
724
|
+
functionVariantKeys.delete(variantEntryNames[i]);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Static-variant keys in this component that override an inherited
|
|
728
|
+
// function variant. Type-level merge says child fully replaces, so the
|
|
729
|
+
// ancestor's function must not run with the child's (object-typed) value.
|
|
730
|
+
let staticVariantsOverridingExtFn: string[] | null = null;
|
|
731
|
+
if (variantEntryCount > 0 && extCount > 0) {
|
|
732
|
+
for (let i = 0; i < variantEntryCount; i++) {
|
|
733
|
+
const name = variantEntryNames[i];
|
|
734
|
+
for (let j = 0; j < extCount; j++) {
|
|
735
|
+
if (extMetas[j].functionVariantKeys.has(name)) {
|
|
736
|
+
if (!staticVariantsOverridingExtFn) {
|
|
737
|
+
staticVariantsOverridingExtFn = [];
|
|
738
|
+
}
|
|
739
|
+
staticVariantsOverridingExtFn.push(name);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
626
743
|
}
|
|
627
744
|
}
|
|
628
|
-
const extMetasWithResolveDefaultsCount = extMetasWithResolveDefaults.length;
|
|
629
745
|
|
|
630
746
|
// Pre-compute static skip key/value sets to pass to extends. These never
|
|
631
747
|
// change across calls — when caller passes no skip sets, we reuse the same
|
|
632
748
|
// object and avoid Set allocation.
|
|
633
749
|
let staticExtSkipKeys: Set<string> | null = null;
|
|
634
|
-
if (
|
|
750
|
+
if (
|
|
751
|
+
hasDisabledVariantKeys ||
|
|
752
|
+
functionVariantCount > 0 ||
|
|
753
|
+
staticVariantsOverridingExtFn !== null
|
|
754
|
+
) {
|
|
635
755
|
staticExtSkipKeys = new Set<string>();
|
|
636
756
|
for (const k of disabledVariantKeys) staticExtSkipKeys.add(k);
|
|
637
|
-
for (let i = 0; i <
|
|
638
|
-
staticExtSkipKeys.add(
|
|
757
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
758
|
+
staticExtSkipKeys.add(functionVariantNames[i]);
|
|
759
|
+
}
|
|
760
|
+
if (staticVariantsOverridingExtFn) {
|
|
761
|
+
for (const k of staticVariantsOverridingExtFn) {
|
|
762
|
+
staticExtSkipKeys.add(k);
|
|
763
|
+
}
|
|
639
764
|
}
|
|
640
765
|
}
|
|
641
766
|
// Skip values are passed directly to extends. We can reuse the same object
|
|
@@ -652,12 +777,12 @@ export function create({
|
|
|
652
777
|
): void {
|
|
653
778
|
if (!hasAnyDisabled) {
|
|
654
779
|
for (const key in input) {
|
|
655
|
-
if (hasOwn
|
|
780
|
+
if (Object.hasOwn(input, key)) out[key] = input[key];
|
|
656
781
|
}
|
|
657
782
|
return;
|
|
658
783
|
}
|
|
659
784
|
for (const key in input) {
|
|
660
|
-
if (!hasOwn
|
|
785
|
+
if (!Object.hasOwn(input, key)) continue;
|
|
661
786
|
if (disabledVariantKeys.has(key)) continue;
|
|
662
787
|
const value = input[key];
|
|
663
788
|
if (hasDisabledVariantValues) {
|
|
@@ -672,12 +797,12 @@ export function create({
|
|
|
672
797
|
|
|
673
798
|
// Pre-create resolveDefaults function — used by parents during their
|
|
674
799
|
// `resolveVariantsHot`. Returns the variants set via setDefaultVariants in
|
|
675
|
-
// the
|
|
800
|
+
// the refine function chain.
|
|
676
801
|
//
|
|
677
|
-
// When this component has no `
|
|
802
|
+
// When this component has no `refine` and no `extend` with work, the
|
|
678
803
|
// function is null — callers can skip iterating it entirely.
|
|
679
804
|
const resolveDefaultsFn: ComponentMeta["resolveDefaults"] =
|
|
680
|
-
|
|
805
|
+
refine || extMetasWithRefineCount > 0
|
|
681
806
|
? (
|
|
682
807
|
childDefaults: Record<string, unknown>,
|
|
683
808
|
userProps: Record<string, unknown> = EMPTY_DEFAULTS,
|
|
@@ -687,52 +812,54 @@ export function create({
|
|
|
687
812
|
const resolvedVariants: Record<string, unknown> = {};
|
|
688
813
|
Object.assign(resolvedVariants, staticDefaults);
|
|
689
814
|
for (const key in childDefaults) {
|
|
690
|
-
if (!hasOwn
|
|
815
|
+
if (!Object.hasOwn(childDefaults, key)) continue;
|
|
691
816
|
const v = childDefaults[key];
|
|
692
817
|
if (v === undefined) continue;
|
|
693
818
|
resolvedVariants[key] = v;
|
|
694
819
|
}
|
|
695
820
|
for (const key in userProps) {
|
|
696
|
-
if (!hasOwn
|
|
821
|
+
if (!Object.hasOwn(userProps, key)) continue;
|
|
697
822
|
const v = userProps[key];
|
|
698
823
|
if (v === undefined) continue;
|
|
699
824
|
resolvedVariants[key] = v;
|
|
700
825
|
}
|
|
701
826
|
|
|
702
|
-
const
|
|
827
|
+
const refineDefaults: Record<string, unknown> = {};
|
|
703
828
|
|
|
704
|
-
for (let i = 0; i <
|
|
705
|
-
const extDefaults =
|
|
706
|
-
|
|
829
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
830
|
+
const extDefaults = extMetasWithRefine[i].resolveDefaults!(
|
|
831
|
+
childDefaults,
|
|
832
|
+
userProps,
|
|
833
|
+
);
|
|
707
834
|
for (const k in extDefaults) {
|
|
708
|
-
if (!hasOwn
|
|
709
|
-
|
|
835
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
836
|
+
refineDefaults[k] = extDefaults[k];
|
|
710
837
|
}
|
|
711
838
|
}
|
|
712
839
|
|
|
713
|
-
if (
|
|
714
|
-
// Filter to own variant keys so `
|
|
840
|
+
if (refine) {
|
|
841
|
+
// Filter to own variant keys so `ctx.variants` matches
|
|
715
842
|
// `VariantValues<V>` when this component is used as an extend by
|
|
716
843
|
// a parent that adds extra variant keys (those keys would
|
|
717
844
|
// otherwise leak through `userProps`).
|
|
718
845
|
const ownVariants: Record<string, unknown> = {};
|
|
719
846
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
720
847
|
const k = variantKeys[i];
|
|
721
|
-
if (hasOwn
|
|
848
|
+
if (Object.hasOwn(resolvedVariants, k)) {
|
|
722
849
|
ownVariants[k] = resolvedVariants[k];
|
|
723
850
|
}
|
|
724
851
|
}
|
|
725
|
-
|
|
852
|
+
refine({
|
|
726
853
|
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
727
854
|
setVariants: noop,
|
|
728
855
|
setDefaultVariants: (newDefaults) => {
|
|
729
856
|
for (const key in newDefaults) {
|
|
730
|
-
if (!hasOwn
|
|
857
|
+
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
731
858
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
732
859
|
if (userProps[key] !== undefined) continue;
|
|
733
860
|
if (isVariantDisabled(config, key)) continue;
|
|
734
861
|
if (isVariantValueDisabled(config, key, value)) continue;
|
|
735
|
-
|
|
862
|
+
refineDefaults[key] = value;
|
|
736
863
|
}
|
|
737
864
|
},
|
|
738
865
|
addClass: noop,
|
|
@@ -740,12 +867,12 @@ export function create({
|
|
|
740
867
|
});
|
|
741
868
|
}
|
|
742
869
|
|
|
743
|
-
return
|
|
870
|
+
return refineDefaults;
|
|
744
871
|
}
|
|
745
872
|
: null;
|
|
746
873
|
|
|
747
874
|
// Hot path: resolve variants by merging static defaults + extends'
|
|
748
|
-
//
|
|
875
|
+
// refine defaults + user-provided props.
|
|
749
876
|
function resolveVariantsHot(
|
|
750
877
|
propsVariants: Record<string, unknown>,
|
|
751
878
|
): Record<string, unknown> {
|
|
@@ -753,14 +880,14 @@ export function create({
|
|
|
753
880
|
const defaults: Record<string, unknown> = {};
|
|
754
881
|
Object.assign(defaults, staticDefaults);
|
|
755
882
|
|
|
756
|
-
// Apply
|
|
883
|
+
// Apply refine defaults from extended components (only those that have
|
|
757
884
|
// actual work to do).
|
|
758
|
-
for (let i = 0; i <
|
|
759
|
-
const meta =
|
|
760
|
-
const
|
|
761
|
-
for (const k in
|
|
762
|
-
if (!hasOwn
|
|
763
|
-
defaults[k] =
|
|
885
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
886
|
+
const meta = extMetasWithRefine[i];
|
|
887
|
+
const extDefaults = meta.resolveDefaults!(defaults, propsVariants);
|
|
888
|
+
for (const k in extDefaults) {
|
|
889
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
890
|
+
defaults[k] = extDefaults[k];
|
|
764
891
|
}
|
|
765
892
|
}
|
|
766
893
|
|
|
@@ -768,7 +895,7 @@ export function create({
|
|
|
768
895
|
// contractually variant-only here — callers building from a full props
|
|
769
896
|
// object filter to variant keys before calling.
|
|
770
897
|
for (const k in propsVariants) {
|
|
771
|
-
if (!hasOwn
|
|
898
|
+
if (!Object.hasOwn(propsVariants, k)) continue;
|
|
772
899
|
const v = propsVariants[k];
|
|
773
900
|
if (v === undefined) continue;
|
|
774
901
|
defaults[k] = v;
|
|
@@ -782,39 +909,44 @@ export function create({
|
|
|
782
909
|
return result;
|
|
783
910
|
}
|
|
784
911
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
912
|
+
const runRefineContext = (
|
|
913
|
+
resolved: Record<string, unknown>,
|
|
914
|
+
userVariantProps: Record<string, unknown>,
|
|
915
|
+
filterOwnVariants: boolean,
|
|
916
|
+
collectOutput: boolean,
|
|
917
|
+
protectedVariants: Record<string, unknown> | null | undefined,
|
|
918
|
+
pendingProtectedVariants: Record<string, unknown> | null | undefined,
|
|
919
|
+
protectedVariantKeys: Set<string> | null | undefined,
|
|
920
|
+
): {
|
|
921
|
+
workingResolved: Record<string, unknown>;
|
|
922
|
+
changedVariants: Record<string, unknown> | null;
|
|
923
|
+
classes: ClassValue[] | null;
|
|
924
|
+
style: StyleValue | null;
|
|
925
|
+
} => {
|
|
799
926
|
let workingResolved = resolved;
|
|
800
927
|
let cClasses: ClassValue[] | null = null;
|
|
801
928
|
let cStyle: StyleValue | null = null;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
929
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
930
|
+
|
|
931
|
+
if (refine) {
|
|
932
|
+
let ownVariants = resolved;
|
|
933
|
+
if (filterOwnVariants) {
|
|
934
|
+
// When this component is being extended, `resolved` is the parent's
|
|
935
|
+
// workingResolved (a superset of our variant keys). Filter to our own
|
|
936
|
+
// keys for `ctx.variants` so the user's `refine` callback sees the
|
|
937
|
+
// shape declared by `VariantValues<V>` and not foreign parent keys.
|
|
938
|
+
const filteredVariants: Record<string, unknown> = {};
|
|
939
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
940
|
+
const k = variantKeys[i];
|
|
941
|
+
if (Object.hasOwn(resolved, k)) filteredVariants[k] = resolved[k];
|
|
942
|
+
}
|
|
943
|
+
ownVariants = filteredVariants;
|
|
812
944
|
}
|
|
813
|
-
// Lazy-init updatedVariants — many
|
|
814
|
-
// or call setDefaultVariants for keys the user already set,
|
|
815
|
-
// copy is unnecessary in the common case.
|
|
945
|
+
// Lazy-init updatedVariants — many refine callbacks only inspect
|
|
946
|
+
// `variants` or call setDefaultVariants for keys the user already set,
|
|
947
|
+
// so the copy is unnecessary in the common case.
|
|
816
948
|
let updatedVariants: Record<string, unknown> | null = null;
|
|
817
|
-
const localCClasses: ClassValue[] = [];
|
|
949
|
+
const localCClasses: ClassValue[] | null = collectOutput ? [] : null;
|
|
818
950
|
let localCStyle: StyleValue | null = null;
|
|
819
951
|
const ensureUpdated = (): Record<string, unknown> => {
|
|
820
952
|
if (updatedVariants) return updatedVariants;
|
|
@@ -823,17 +955,40 @@ export function create({
|
|
|
823
955
|
updatedVariants = u;
|
|
824
956
|
return u;
|
|
825
957
|
};
|
|
958
|
+
const setChangedVariant = (
|
|
959
|
+
key: string,
|
|
960
|
+
value: unknown,
|
|
961
|
+
protect = false,
|
|
962
|
+
) => {
|
|
963
|
+
if (shouldCollectChangedVariants) {
|
|
964
|
+
if (!changedVariants) changedVariants = {};
|
|
965
|
+
changedVariants[key] = value;
|
|
966
|
+
}
|
|
967
|
+
if (protect && protectedVariants) {
|
|
968
|
+
protectedVariants[key] = value;
|
|
969
|
+
protectedVariantKeys?.add(key);
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const getCurrentVariantValue = (key: string) => {
|
|
973
|
+
return updatedVariants ? updatedVariants[key] : ownVariants[key];
|
|
974
|
+
};
|
|
826
975
|
const ctx = {
|
|
827
976
|
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
828
977
|
setVariants: (
|
|
829
978
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
830
979
|
) => {
|
|
831
980
|
if (!hasAnyDisabled) {
|
|
832
|
-
|
|
981
|
+
for (const key in newVariants) {
|
|
982
|
+
if (!Object.hasOwn(newVariants, key)) continue;
|
|
983
|
+
const value = (newVariants as Record<string, unknown>)[key];
|
|
984
|
+
setChangedVariant(key, value, true);
|
|
985
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
986
|
+
ensureUpdated()[key] = value;
|
|
987
|
+
}
|
|
833
988
|
return;
|
|
834
989
|
}
|
|
835
990
|
for (const key in newVariants) {
|
|
836
|
-
if (!hasOwn
|
|
991
|
+
if (!Object.hasOwn(newVariants, key)) continue;
|
|
837
992
|
if (disabledVariantKeys.has(key)) continue;
|
|
838
993
|
const value = (newVariants as Record<string, unknown>)[key];
|
|
839
994
|
if (hasDisabledVariantValues) {
|
|
@@ -845,6 +1000,8 @@ export function create({
|
|
|
845
1000
|
continue;
|
|
846
1001
|
}
|
|
847
1002
|
}
|
|
1003
|
+
setChangedVariant(key, value, true);
|
|
1004
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
848
1005
|
ensureUpdated()[key] = value;
|
|
849
1006
|
}
|
|
850
1007
|
},
|
|
@@ -852,8 +1009,9 @@ export function create({
|
|
|
852
1009
|
newDefaults: VariantValues<Record<string, unknown>>,
|
|
853
1010
|
) => {
|
|
854
1011
|
for (const key in newDefaults) {
|
|
855
|
-
if (!hasOwn
|
|
1012
|
+
if (!Object.hasOwn(newDefaults, key)) continue;
|
|
856
1013
|
if (userVariantProps[key] !== undefined) continue;
|
|
1014
|
+
if (protectedVariantKeys?.has(key)) continue;
|
|
857
1015
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
858
1016
|
if (hasAnyDisabled) {
|
|
859
1017
|
if (disabledVariantKeys.has(key)) continue;
|
|
@@ -865,21 +1023,27 @@ export function create({
|
|
|
865
1023
|
continue;
|
|
866
1024
|
}
|
|
867
1025
|
}
|
|
1026
|
+
setChangedVariant(key, value);
|
|
1027
|
+
if (pendingProtectedVariants) {
|
|
1028
|
+
pendingProtectedVariants[key] = value;
|
|
1029
|
+
}
|
|
1030
|
+
if (getCurrentVariantValue(key) === value) continue;
|
|
868
1031
|
ensureUpdated()[key] = value;
|
|
869
1032
|
}
|
|
870
1033
|
},
|
|
871
1034
|
addClass: (className: ClassValue) => {
|
|
872
|
-
localCClasses
|
|
1035
|
+
localCClasses?.push(className);
|
|
873
1036
|
},
|
|
874
1037
|
addStyle: (newStyle: StyleValue) => {
|
|
1038
|
+
if (!collectOutput) return;
|
|
875
1039
|
if (!localCStyle) localCStyle = {};
|
|
876
1040
|
Object.assign(localCStyle, newStyle);
|
|
877
1041
|
},
|
|
878
1042
|
};
|
|
879
|
-
const result =
|
|
880
|
-
if (result != null) {
|
|
1043
|
+
const result = refine(ctx);
|
|
1044
|
+
if (collectOutput && result != null) {
|
|
881
1045
|
const r = extractClassAndStylePrebuilt(result);
|
|
882
|
-
if (r.class != null) localCClasses
|
|
1046
|
+
if (r.class != null) localCClasses?.push(r.class);
|
|
883
1047
|
if (r.style) {
|
|
884
1048
|
if (!localCStyle) localCStyle = {};
|
|
885
1049
|
Object.assign(localCStyle, r.style);
|
|
@@ -888,79 +1052,133 @@ export function create({
|
|
|
888
1052
|
cClasses = localCClasses;
|
|
889
1053
|
cStyle = localCStyle;
|
|
890
1054
|
if (updatedVariants) {
|
|
1055
|
+
const nextResolved: Record<string, unknown> = {};
|
|
1056
|
+
Object.assign(nextResolved, workingResolved);
|
|
891
1057
|
if (hasAnyDisabled) {
|
|
892
1058
|
const filteredUpdated: Record<string, unknown> = {};
|
|
893
1059
|
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
894
|
-
|
|
1060
|
+
Object.assign(nextResolved, filteredUpdated);
|
|
895
1061
|
} else {
|
|
896
|
-
|
|
1062
|
+
Object.assign(nextResolved, updatedVariants);
|
|
897
1063
|
}
|
|
1064
|
+
workingResolved = nextResolved;
|
|
898
1065
|
}
|
|
899
1066
|
}
|
|
900
1067
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
} else {
|
|
909
|
-
extSkipKeys = new Set(skipKeys);
|
|
910
|
-
for (const k of staticExtSkipKeys) extSkipKeys.add(k);
|
|
911
|
-
}
|
|
1068
|
+
return {
|
|
1069
|
+
workingResolved,
|
|
1070
|
+
changedVariants,
|
|
1071
|
+
classes: cClasses,
|
|
1072
|
+
style: cStyle,
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
912
1075
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1076
|
+
// Core compute path. Called both for top-level rendering (via
|
|
1077
|
+
// `computeResult`) and recursively when this component is used as an
|
|
1078
|
+
// `extend` target by another component. Pushes variant classes (excluding
|
|
1079
|
+
// base class) into `classesOut` and merges styles into `styleOut`.
|
|
1080
|
+
const computeOnce: ComputeFn = (
|
|
1081
|
+
resolved,
|
|
1082
|
+
userVariantProps,
|
|
1083
|
+
skipKeys,
|
|
1084
|
+
skipValues,
|
|
1085
|
+
classesOut,
|
|
1086
|
+
styleOut,
|
|
1087
|
+
runState,
|
|
1088
|
+
protectedVariants,
|
|
1089
|
+
pendingProtectedVariants,
|
|
1090
|
+
protectedVariantKeys,
|
|
1091
|
+
) => {
|
|
1092
|
+
// Run `refine` (if any). May modify resolved variants and emit classes
|
|
1093
|
+
// and styles.
|
|
1094
|
+
let workingResolved = resolved;
|
|
1095
|
+
let cClasses: ClassValue[] | null = null;
|
|
1096
|
+
let cStyle: StyleValue | null = null;
|
|
1097
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
1098
|
+
if (refine) {
|
|
1099
|
+
const refineResult = runRefineContext(
|
|
1100
|
+
resolved,
|
|
1101
|
+
userVariantProps,
|
|
1102
|
+
true,
|
|
1103
|
+
true,
|
|
1104
|
+
protectedVariants,
|
|
1105
|
+
pendingProtectedVariants,
|
|
1106
|
+
protectedVariantKeys,
|
|
1107
|
+
);
|
|
1108
|
+
workingResolved = refineResult.workingResolved;
|
|
1109
|
+
cClasses = refineResult.classes;
|
|
1110
|
+
cStyle = refineResult.style;
|
|
1111
|
+
changedVariants = refineResult.changedVariants;
|
|
933
1112
|
}
|
|
934
1113
|
|
|
935
1114
|
// Run extends' contributions first (their full classes + styles) so our
|
|
936
1115
|
// own base style and variants apply on top, matching the original
|
|
937
1116
|
// ext1 → ext2 → … → current ordering.
|
|
938
1117
|
//
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
//
|
|
942
|
-
// the
|
|
943
|
-
// "user-provided" makes extends' own `setDefaultVariants` skip them, so
|
|
944
|
-
// extends emit variant classes that match what we resolved (rather than
|
|
945
|
-
// re-running their own defaults and emitting a different class).
|
|
946
|
-
// Replacing this with the original `userVariantProps` looks cleaner but
|
|
947
|
-
// breaks "child computed setDefaultVariants overrides parent computed
|
|
948
|
-
// setDefaultVariants" in `tests/computed.test.ts` — extends would then
|
|
949
|
-
// overwrite values the descendant already resolved.
|
|
1118
|
+
// Pass explicit user values plus refine changes as the extends'
|
|
1119
|
+
// `userVariantProps`. This lets more-specific refine decisions stick
|
|
1120
|
+
// across re-runs while inherited static defaults can still be refined by
|
|
1121
|
+
// the extended component's own refine chain.
|
|
950
1122
|
if (hasExtend) {
|
|
1123
|
+
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
1124
|
+
// caller-provided sets need merging.
|
|
1125
|
+
let extSkipKeys: Set<string> | null;
|
|
1126
|
+
if (skipKeys === null) {
|
|
1127
|
+
extSkipKeys = staticExtSkipKeys;
|
|
1128
|
+
} else if (staticExtSkipKeys === null) {
|
|
1129
|
+
extSkipKeys = skipKeys;
|
|
1130
|
+
} else {
|
|
1131
|
+
extSkipKeys = new Set(skipKeys);
|
|
1132
|
+
for (const k of staticExtSkipKeys) extSkipKeys.add(k);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
let extSkipVals: Record<string, Set<string>> | null;
|
|
1136
|
+
if (skipValues === null) {
|
|
1137
|
+
extSkipVals = staticExtSkipValues;
|
|
1138
|
+
} else if (staticExtSkipValues === null) {
|
|
1139
|
+
extSkipVals = skipValues;
|
|
1140
|
+
} else {
|
|
1141
|
+
extSkipVals = {};
|
|
1142
|
+
for (const k in skipValues) {
|
|
1143
|
+
extSkipVals[k] = skipValues[k];
|
|
1144
|
+
}
|
|
1145
|
+
for (const k in staticExtSkipValues) {
|
|
1146
|
+
const existing = extSkipVals[k];
|
|
1147
|
+
if (existing) {
|
|
1148
|
+
const merged = new Set<string>(existing);
|
|
1149
|
+
for (const v of staticExtSkipValues[k]) merged.add(v);
|
|
1150
|
+
extSkipVals[k] = merged;
|
|
1151
|
+
} else {
|
|
1152
|
+
extSkipVals[k] = staticExtSkipValues[k];
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const extUserVariantProps =
|
|
1158
|
+
extMetasWithRefineCount > 0
|
|
1159
|
+
? getExtUserVariantProps(
|
|
1160
|
+
userVariantProps,
|
|
1161
|
+
protectedVariants ?? null,
|
|
1162
|
+
changedVariants,
|
|
1163
|
+
)
|
|
1164
|
+
: userVariantProps;
|
|
951
1165
|
for (let i = 0; i < extCount; i++) {
|
|
952
1166
|
if (hasIsolatedExt && extIsolated[i]) {
|
|
953
1167
|
// Isolated extend (different factory): gather its variant classes
|
|
954
1168
|
// into a scratch array, then push the joined string after applying
|
|
955
1169
|
// its own transformClass. Our outer transform applies on top.
|
|
956
1170
|
const extClasses: ClsxClassValue[] = [];
|
|
957
|
-
extMetas[i].compute(
|
|
958
|
-
workingResolved,
|
|
1171
|
+
workingResolved = extMetas[i].compute(
|
|
959
1172
|
workingResolved,
|
|
1173
|
+
extUserVariantProps,
|
|
960
1174
|
extSkipKeys,
|
|
961
1175
|
extSkipVals,
|
|
962
1176
|
extClasses,
|
|
963
1177
|
styleOut,
|
|
1178
|
+
runState,
|
|
1179
|
+
protectedVariants,
|
|
1180
|
+
pendingProtectedVariants,
|
|
1181
|
+
protectedVariantKeys,
|
|
964
1182
|
);
|
|
965
1183
|
if (extClasses.length > 0) {
|
|
966
1184
|
const joined = clsx(extClasses);
|
|
@@ -971,15 +1189,24 @@ export function create({
|
|
|
971
1189
|
}
|
|
972
1190
|
}
|
|
973
1191
|
} else {
|
|
974
|
-
extMetas[i].compute(
|
|
975
|
-
workingResolved,
|
|
1192
|
+
workingResolved = extMetas[i].compute(
|
|
976
1193
|
workingResolved,
|
|
1194
|
+
extUserVariantProps,
|
|
977
1195
|
extSkipKeys,
|
|
978
1196
|
extSkipVals,
|
|
979
1197
|
classesOut,
|
|
980
1198
|
styleOut,
|
|
1199
|
+
runState,
|
|
1200
|
+
protectedVariants,
|
|
1201
|
+
pendingProtectedVariants,
|
|
1202
|
+
protectedVariantKeys,
|
|
981
1203
|
);
|
|
982
1204
|
}
|
|
1205
|
+
// Only sync protected variants when a child refine resolver can
|
|
1206
|
+
// observe them. Otherwise extUserVariantProps may alias caller props.
|
|
1207
|
+
if (protectedVariants && extMetasWithRefineCount > 0) {
|
|
1208
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
1209
|
+
}
|
|
983
1210
|
}
|
|
984
1211
|
}
|
|
985
1212
|
|
|
@@ -987,9 +1214,10 @@ export function create({
|
|
|
987
1214
|
if (hasBaseStyle) Object.assign(styleOut, baseStyle);
|
|
988
1215
|
|
|
989
1216
|
// Apply own variants. Skip keys/values come from caller (e.g., parent
|
|
990
|
-
// wants its own
|
|
1217
|
+
// wants its own function variant to override this variant).
|
|
991
1218
|
// `variantEntryNames` already excludes disabled keys (those with `null`
|
|
992
|
-
// value in config), so we don't re-check
|
|
1219
|
+
// value in config) and function variants, so we don't re-check
|
|
1220
|
+
// `disabledVariantKeys` here.
|
|
993
1221
|
const ownSkipKeys = skipKeys;
|
|
994
1222
|
const ownSkipValues = skipValues;
|
|
995
1223
|
for (let i = 0; i < variantEntryCount; i++) {
|
|
@@ -1027,9 +1255,11 @@ export function create({
|
|
|
1027
1255
|
}
|
|
1028
1256
|
}
|
|
1029
1257
|
|
|
1030
|
-
// Apply
|
|
1031
|
-
|
|
1032
|
-
|
|
1258
|
+
// Apply function variants — entries in `variants` whose value is a
|
|
1259
|
+
// function. They run after static variants and override any same-key
|
|
1260
|
+
// inherited variant via `staticExtSkipKeys`.
|
|
1261
|
+
for (let i = 0; i < functionVariantCount; i++) {
|
|
1262
|
+
const variantName = functionVariantNames[i];
|
|
1033
1263
|
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
1034
1264
|
const selectedValue = workingResolved[variantName];
|
|
1035
1265
|
if (selectedValue === undefined) continue;
|
|
@@ -1041,7 +1271,7 @@ export function create({
|
|
|
1041
1271
|
) {
|
|
1042
1272
|
continue;
|
|
1043
1273
|
}
|
|
1044
|
-
const fn =
|
|
1274
|
+
const fn = functionVariantFns[i];
|
|
1045
1275
|
const computedResult = fn(selectedValue);
|
|
1046
1276
|
if (computedResult == null) continue;
|
|
1047
1277
|
const r = extractClassAndStylePrebuilt(computedResult);
|
|
@@ -1049,15 +1279,274 @@ export function create({
|
|
|
1049
1279
|
if (r.style) Object.assign(styleOut, r.style);
|
|
1050
1280
|
}
|
|
1051
1281
|
|
|
1052
|
-
// Apply `
|
|
1282
|
+
// Apply `refine` results — must come after own variants (static and
|
|
1283
|
+
// function).
|
|
1053
1284
|
if (cClasses) {
|
|
1054
1285
|
for (let i = 0; i < cClasses.length; i++) {
|
|
1055
1286
|
classesOut.push(cClasses[i] as ClsxClassValue);
|
|
1056
1287
|
}
|
|
1057
1288
|
}
|
|
1058
1289
|
if (cStyle) Object.assign(styleOut, cStyle);
|
|
1290
|
+
|
|
1291
|
+
return workingResolved;
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const compute: ComputeFn =
|
|
1295
|
+
!refine && extMetasWithRefineCount === 0
|
|
1296
|
+
? (
|
|
1297
|
+
resolved,
|
|
1298
|
+
userVariantProps,
|
|
1299
|
+
skipKeys,
|
|
1300
|
+
skipValues,
|
|
1301
|
+
classesOut,
|
|
1302
|
+
styleOut,
|
|
1303
|
+
runState,
|
|
1304
|
+
protectedVariants,
|
|
1305
|
+
pendingProtectedVariants,
|
|
1306
|
+
protectedVariantKeys,
|
|
1307
|
+
) => {
|
|
1308
|
+
return computeOnce(
|
|
1309
|
+
resolved,
|
|
1310
|
+
userVariantProps,
|
|
1311
|
+
skipKeys,
|
|
1312
|
+
skipValues,
|
|
1313
|
+
classesOut,
|
|
1314
|
+
styleOut,
|
|
1315
|
+
runState,
|
|
1316
|
+
protectedVariants,
|
|
1317
|
+
pendingProtectedVariants,
|
|
1318
|
+
protectedVariantKeys,
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
: (
|
|
1322
|
+
resolved,
|
|
1323
|
+
userVariantProps,
|
|
1324
|
+
skipKeys,
|
|
1325
|
+
skipValues,
|
|
1326
|
+
classesOut,
|
|
1327
|
+
styleOut,
|
|
1328
|
+
runState,
|
|
1329
|
+
protectedVariants,
|
|
1330
|
+
pendingProtectedVariants,
|
|
1331
|
+
protectedVariantKeys,
|
|
1332
|
+
) => {
|
|
1333
|
+
runState ??= { remaining: MAX_REFINE_RUNS, warned: false };
|
|
1334
|
+
protectedVariants ??= {};
|
|
1335
|
+
protectedVariantKeys ??= new Set<string>();
|
|
1336
|
+
let workingResolved = resolved;
|
|
1337
|
+
let lastClasses: ClsxClassValue[] = [];
|
|
1338
|
+
let lastStyle: StyleValue = {};
|
|
1339
|
+
let isFirstRun = true;
|
|
1340
|
+
|
|
1341
|
+
while (runState.remaining > 0) {
|
|
1342
|
+
runState.remaining -= 1;
|
|
1343
|
+
let useDirectOutput = isFirstRun;
|
|
1344
|
+
if (useDirectOutput) {
|
|
1345
|
+
for (const key in styleOut) {
|
|
1346
|
+
if (Object.hasOwn(styleOut, key)) {
|
|
1347
|
+
useDirectOutput = false;
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const classCount = classesOut.length;
|
|
1353
|
+
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1354
|
+
const nextClasses: ClsxClassValue[] = useDirectOutput
|
|
1355
|
+
? classesOut
|
|
1356
|
+
: [];
|
|
1357
|
+
const nextStyle: StyleValue = useDirectOutput ? styleOut : {};
|
|
1358
|
+
const nextResolved = computeOnce(
|
|
1359
|
+
workingResolved,
|
|
1360
|
+
userVariantProps,
|
|
1361
|
+
skipKeys,
|
|
1362
|
+
skipValues,
|
|
1363
|
+
nextClasses,
|
|
1364
|
+
nextStyle,
|
|
1365
|
+
runState,
|
|
1366
|
+
protectedVariants,
|
|
1367
|
+
nextPendingProtectedVariants,
|
|
1368
|
+
protectedVariantKeys,
|
|
1369
|
+
);
|
|
1370
|
+
|
|
1371
|
+
let protectedChanged: boolean;
|
|
1372
|
+
if (pendingProtectedVariants) {
|
|
1373
|
+
protectedChanged = mergeVariants(
|
|
1374
|
+
pendingProtectedVariants,
|
|
1375
|
+
nextPendingProtectedVariants,
|
|
1376
|
+
protectedVariantKeys,
|
|
1377
|
+
);
|
|
1378
|
+
} else {
|
|
1379
|
+
protectedChanged = mergeVariants(
|
|
1380
|
+
protectedVariants,
|
|
1381
|
+
nextPendingProtectedVariants,
|
|
1382
|
+
protectedVariantKeys,
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (
|
|
1387
|
+
!protectedChanged &&
|
|
1388
|
+
(nextResolved === workingResolved ||
|
|
1389
|
+
areVariantsEqual(workingResolved, nextResolved))
|
|
1390
|
+
) {
|
|
1391
|
+
if (!useDirectOutput) {
|
|
1392
|
+
for (let i = 0; i < nextClasses.length; i++) {
|
|
1393
|
+
classesOut.push(nextClasses[i]);
|
|
1394
|
+
}
|
|
1395
|
+
Object.assign(styleOut, nextStyle);
|
|
1396
|
+
}
|
|
1397
|
+
return nextResolved;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (useDirectOutput && runState.remaining === 0) {
|
|
1401
|
+
// Keep the direct output from the last allowed run. Rolling
|
|
1402
|
+
// back here would drop it before the fallback copy below.
|
|
1403
|
+
warnRefineLimit(runState);
|
|
1404
|
+
return nextResolved;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (useDirectOutput) {
|
|
1408
|
+
classesOut.length = classCount;
|
|
1409
|
+
for (const key in styleOut) {
|
|
1410
|
+
if (Object.hasOwn(styleOut, key)) {
|
|
1411
|
+
Reflect.deleteProperty(styleOut, key);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
} else {
|
|
1415
|
+
lastClasses = nextClasses;
|
|
1416
|
+
lastStyle = nextStyle;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
workingResolved = nextResolved;
|
|
1420
|
+
isFirstRun = false;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
warnRefineLimit(runState);
|
|
1424
|
+
|
|
1425
|
+
for (let i = 0; i < lastClasses.length; i++) {
|
|
1426
|
+
classesOut.push(lastClasses[i]);
|
|
1427
|
+
}
|
|
1428
|
+
Object.assign(styleOut, lastStyle);
|
|
1429
|
+
return workingResolved;
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
const resolveRefineOnce: ResolveRefineFn = (
|
|
1433
|
+
resolved,
|
|
1434
|
+
userVariantProps,
|
|
1435
|
+
filterOwnVariants = true,
|
|
1436
|
+
runState,
|
|
1437
|
+
protectedVariants,
|
|
1438
|
+
pendingProtectedVariants,
|
|
1439
|
+
protectedVariantKeys,
|
|
1440
|
+
) => {
|
|
1441
|
+
let workingResolved = resolved;
|
|
1442
|
+
let changedVariants: Record<string, unknown> | null = null;
|
|
1443
|
+
if (refine) {
|
|
1444
|
+
const refineResult = runRefineContext(
|
|
1445
|
+
resolved,
|
|
1446
|
+
userVariantProps,
|
|
1447
|
+
filterOwnVariants,
|
|
1448
|
+
false,
|
|
1449
|
+
protectedVariants,
|
|
1450
|
+
pendingProtectedVariants,
|
|
1451
|
+
protectedVariantKeys,
|
|
1452
|
+
);
|
|
1453
|
+
workingResolved = refineResult.workingResolved;
|
|
1454
|
+
changedVariants = refineResult.changedVariants;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (extMetasWithRefineCount > 0) {
|
|
1458
|
+
const extUserVariantProps = getExtUserVariantProps(
|
|
1459
|
+
userVariantProps,
|
|
1460
|
+
protectedVariants ?? null,
|
|
1461
|
+
changedVariants,
|
|
1462
|
+
);
|
|
1463
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1464
|
+
const meta = extMetasWithRefine[i];
|
|
1465
|
+
const resolveRefine = meta.resolveRefine;
|
|
1466
|
+
if (!resolveRefine) continue;
|
|
1467
|
+
workingResolved = resolveRefine(
|
|
1468
|
+
workingResolved,
|
|
1469
|
+
extUserVariantProps,
|
|
1470
|
+
true,
|
|
1471
|
+
runState,
|
|
1472
|
+
protectedVariants,
|
|
1473
|
+
pendingProtectedVariants,
|
|
1474
|
+
protectedVariantKeys,
|
|
1475
|
+
);
|
|
1476
|
+
if (protectedVariants) {
|
|
1477
|
+
Object.assign(extUserVariantProps, protectedVariants);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
return workingResolved;
|
|
1059
1483
|
};
|
|
1060
1484
|
|
|
1485
|
+
const resolveRefine: ResolveRefineFn | null =
|
|
1486
|
+
refine || extMetasWithRefineCount > 0
|
|
1487
|
+
? (
|
|
1488
|
+
resolved,
|
|
1489
|
+
userVariantProps,
|
|
1490
|
+
filterOwnVariants = true,
|
|
1491
|
+
runState,
|
|
1492
|
+
protectedVariants,
|
|
1493
|
+
pendingProtectedVariants,
|
|
1494
|
+
protectedVariantKeys,
|
|
1495
|
+
) => {
|
|
1496
|
+
runState ??= { remaining: MAX_REFINE_RUNS, warned: false };
|
|
1497
|
+
protectedVariants ??= {};
|
|
1498
|
+
protectedVariantKeys ??= new Set<string>();
|
|
1499
|
+
let workingResolved = resolved;
|
|
1500
|
+
let reachedLimit = true;
|
|
1501
|
+
|
|
1502
|
+
while (runState.remaining > 0) {
|
|
1503
|
+
runState.remaining -= 1;
|
|
1504
|
+
const nextPendingProtectedVariants: Record<string, unknown> = {};
|
|
1505
|
+
const nextResolved = resolveRefineOnce(
|
|
1506
|
+
workingResolved,
|
|
1507
|
+
userVariantProps,
|
|
1508
|
+
filterOwnVariants,
|
|
1509
|
+
runState,
|
|
1510
|
+
protectedVariants,
|
|
1511
|
+
nextPendingProtectedVariants,
|
|
1512
|
+
protectedVariantKeys,
|
|
1513
|
+
);
|
|
1514
|
+
let protectedChanged: boolean;
|
|
1515
|
+
if (pendingProtectedVariants) {
|
|
1516
|
+
protectedChanged = mergeVariants(
|
|
1517
|
+
pendingProtectedVariants,
|
|
1518
|
+
nextPendingProtectedVariants,
|
|
1519
|
+
protectedVariantKeys,
|
|
1520
|
+
);
|
|
1521
|
+
} else {
|
|
1522
|
+
protectedChanged = mergeVariants(
|
|
1523
|
+
protectedVariants,
|
|
1524
|
+
nextPendingProtectedVariants,
|
|
1525
|
+
protectedVariantKeys,
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (
|
|
1530
|
+
!protectedChanged &&
|
|
1531
|
+
(nextResolved === workingResolved ||
|
|
1532
|
+
areVariantsEqual(workingResolved, nextResolved))
|
|
1533
|
+
) {
|
|
1534
|
+
workingResolved = nextResolved;
|
|
1535
|
+
reachedLimit = false;
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
workingResolved = nextResolved;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (reachedLimit) {
|
|
1543
|
+
warnRefineLimit(runState);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
return workingResolved;
|
|
1547
|
+
}
|
|
1548
|
+
: null;
|
|
1549
|
+
|
|
1061
1550
|
// Top-level: resolves variants from user props, calls compute, then
|
|
1062
1551
|
// assembles className and style with user-provided class/style overrides.
|
|
1063
1552
|
const computeResult = (
|
|
@@ -1073,26 +1562,26 @@ export function create({
|
|
|
1073
1562
|
Object.assign(resolved, staticDefaults);
|
|
1074
1563
|
|
|
1075
1564
|
let userVariantProps: Record<string, unknown>;
|
|
1076
|
-
if (
|
|
1565
|
+
if (extMetasWithRefineCount > 0) {
|
|
1077
1566
|
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1078
1567
|
// object as `userProps`, so we extract one.
|
|
1079
1568
|
const variantProps: Record<string, unknown> = {};
|
|
1080
1569
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
1081
1570
|
const key = variantKeys[i];
|
|
1082
|
-
if (hasOwn
|
|
1571
|
+
if (Object.hasOwn(propsRecord, key)) {
|
|
1083
1572
|
variantProps[key] = propsRecord[key];
|
|
1084
1573
|
}
|
|
1085
1574
|
}
|
|
1086
|
-
for (let i = 0; i <
|
|
1087
|
-
const meta =
|
|
1088
|
-
const
|
|
1089
|
-
for (const k in
|
|
1090
|
-
if (!hasOwn
|
|
1091
|
-
resolved[k] =
|
|
1575
|
+
for (let i = 0; i < extMetasWithRefineCount; i++) {
|
|
1576
|
+
const meta = extMetasWithRefine[i];
|
|
1577
|
+
const extDefaults = meta.resolveDefaults!(resolved, variantProps);
|
|
1578
|
+
for (const k in extDefaults) {
|
|
1579
|
+
if (!Object.hasOwn(extDefaults, k)) continue;
|
|
1580
|
+
resolved[k] = extDefaults[k];
|
|
1092
1581
|
}
|
|
1093
1582
|
}
|
|
1094
1583
|
for (const k in variantProps) {
|
|
1095
|
-
if (!hasOwn
|
|
1584
|
+
if (!Object.hasOwn(variantProps, k)) continue;
|
|
1096
1585
|
const v = variantProps[k];
|
|
1097
1586
|
if (v === undefined) continue;
|
|
1098
1587
|
resolved[k] = v;
|
|
@@ -1100,11 +1589,11 @@ export function create({
|
|
|
1100
1589
|
userVariantProps = variantProps;
|
|
1101
1590
|
} else {
|
|
1102
1591
|
// Fast path: walk variantKeys directly against propsRecord. Use
|
|
1103
|
-
// hasOwn so a polluted Object.prototype can't introduce variant
|
|
1592
|
+
// Object.hasOwn so a polluted Object.prototype can't introduce variant
|
|
1104
1593
|
// values the user didn't pass.
|
|
1105
1594
|
for (let i = 0; i < variantKeysLength; i++) {
|
|
1106
1595
|
const key = variantKeys[i];
|
|
1107
|
-
if (!hasOwn
|
|
1596
|
+
if (!Object.hasOwn(propsRecord, key)) continue;
|
|
1108
1597
|
const v = propsRecord[key];
|
|
1109
1598
|
if (v === undefined) continue;
|
|
1110
1599
|
resolved[key] = v;
|
|
@@ -1162,67 +1651,8 @@ export function create({
|
|
|
1162
1651
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1163
1652
|
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1164
1653
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
if (computed) {
|
|
1168
|
-
const updatedVariants: Record<string, unknown> = {};
|
|
1169
|
-
Object.assign(updatedVariants, resolvedVariants);
|
|
1170
|
-
const ctx = {
|
|
1171
|
-
variants: resolvedVariants as VariantValues<Record<string, unknown>>,
|
|
1172
|
-
setVariants: (
|
|
1173
|
-
newVariants: VariantValues<Record<string, unknown>>,
|
|
1174
|
-
) => {
|
|
1175
|
-
if (!hasAnyDisabled) {
|
|
1176
|
-
Object.assign(updatedVariants, newVariants);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
for (const key in newVariants) {
|
|
1180
|
-
if (!hasOwn.call(newVariants, key)) continue;
|
|
1181
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1182
|
-
const value = (newVariants as Record<string, unknown>)[key];
|
|
1183
|
-
if (hasDisabledVariantValues) {
|
|
1184
|
-
const valueKey = getVariantValueKey(value);
|
|
1185
|
-
if (
|
|
1186
|
-
valueKey != null &&
|
|
1187
|
-
disabledVariantValues[key]?.has(valueKey)
|
|
1188
|
-
) {
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
updatedVariants[key] = value;
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
setDefaultVariants: (
|
|
1196
|
-
newDefaults: VariantValues<Record<string, unknown>>,
|
|
1197
|
-
) => {
|
|
1198
|
-
for (const key in newDefaults) {
|
|
1199
|
-
if (!hasOwn.call(newDefaults, key)) continue;
|
|
1200
|
-
if (variantProps[key] !== undefined) continue;
|
|
1201
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
1202
|
-
if (hasAnyDisabled) {
|
|
1203
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1204
|
-
const valueKey = getVariantValueKey(value);
|
|
1205
|
-
if (
|
|
1206
|
-
valueKey != null &&
|
|
1207
|
-
disabledVariantValues[key]?.has(valueKey)
|
|
1208
|
-
) {
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
updatedVariants[key] = value;
|
|
1213
|
-
}
|
|
1214
|
-
},
|
|
1215
|
-
addClass: noop,
|
|
1216
|
-
addStyle: noop,
|
|
1217
|
-
};
|
|
1218
|
-
computed(ctx);
|
|
1219
|
-
if (hasAnyDisabled) {
|
|
1220
|
-
const filteredUpdated: Record<string, unknown> = {};
|
|
1221
|
-
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
1222
|
-
resolvedVariants = filteredUpdated;
|
|
1223
|
-
} else {
|
|
1224
|
-
resolvedVariants = updatedVariants;
|
|
1225
|
-
}
|
|
1654
|
+
if (resolveRefine) {
|
|
1655
|
+
resolvedVariants = resolveRefine(resolvedVariants, variantProps, false);
|
|
1226
1656
|
}
|
|
1227
1657
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1228
1658
|
};
|
|
@@ -1233,10 +1663,12 @@ export function create({
|
|
|
1233
1663
|
// `transformClass(clsx(allClasses))` at render time, so applying it here
|
|
1234
1664
|
// would compound (double for own-render, triple+ for extend chains) and
|
|
1235
1665
|
// misbehave for non-idempotent transforms.
|
|
1236
|
-
const computedBaseClass =
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1666
|
+
const computedBaseClass = hasExtend
|
|
1667
|
+
? clsx(
|
|
1668
|
+
...(extBaseClassesArr as ClsxClassValue[]),
|
|
1669
|
+
config.class as ClsxClassValue,
|
|
1670
|
+
)
|
|
1671
|
+
: clsx(config.class as ClsxClassValue);
|
|
1240
1672
|
|
|
1241
1673
|
// Shared closures across the default and modal components.
|
|
1242
1674
|
const classFn = (props: ComponentProps<MergedVariants> = {}) => {
|
|
@@ -1247,7 +1679,9 @@ export function create({
|
|
|
1247
1679
|
staticDefaults,
|
|
1248
1680
|
resolveDefaults: resolveDefaultsFn,
|
|
1249
1681
|
compute,
|
|
1682
|
+
resolveRefine,
|
|
1250
1683
|
transformClass,
|
|
1684
|
+
functionVariantKeys,
|
|
1251
1685
|
};
|
|
1252
1686
|
|
|
1253
1687
|
const initComponent = <
|
|
@@ -1255,15 +1689,14 @@ export function create({
|
|
|
1255
1689
|
T extends ModalComponent<MergedVariants, R>,
|
|
1256
1690
|
>(
|
|
1257
1691
|
c: T,
|
|
1258
|
-
|
|
1692
|
+
propKeys: string[],
|
|
1259
1693
|
style: T["style"],
|
|
1260
1694
|
): T => {
|
|
1261
1695
|
c.class = classFn;
|
|
1262
1696
|
c.style = style;
|
|
1263
1697
|
c.getVariants = getVariants;
|
|
1264
|
-
c.keys = keys;
|
|
1265
1698
|
c.variantKeys = variantKeys;
|
|
1266
|
-
c.propKeys =
|
|
1699
|
+
c.propKeys = propKeys;
|
|
1267
1700
|
setComponentMeta(c, meta);
|
|
1268
1701
|
return c;
|
|
1269
1702
|
};
|
|
@@ -1272,7 +1705,7 @@ export function create({
|
|
|
1272
1705
|
const defaultComponent = ((props: ComponentProps<MergedVariants> = {}) => {
|
|
1273
1706
|
const { className, style } = computeResult(props);
|
|
1274
1707
|
return { class: className, style };
|
|
1275
|
-
}) as CVComponent<V,
|
|
1708
|
+
}) as CVComponent<V, E>;
|
|
1276
1709
|
initComponent(defaultComponent, inputPropsKeys, (props = {}) => {
|
|
1277
1710
|
return computeResult(props).style;
|
|
1278
1711
|
});
|