clava 0.2.1 → 0.2.3
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 +20 -0
- package/dist/index.js +303 -232
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +506 -397
- package/src/utils.ts +47 -3
- package/tests/{component-api-test.ts → component-api.test.ts} +39 -0
- package/tests/{computed-test.ts → computed.test.ts} +57 -0
- package/tests/{extend-test.ts → extend.test.ts} +81 -0
- package/tests/prototype-pollution.test.ts +47 -0
- package/perfs/component.bench.ts +0 -233
- /package/tests/{class-style-test.ts → class-style.test.ts} +0 -0
- /package/tests/{computed-variants-test.ts → computed-variants.test.ts} +0 -0
- /package/tests/{language-service-test.ts → language-service.test.ts} +0 -0
- /package/tests/{react-test.ts → react.test.ts} +0 -0
- /package/tests/{solid-test.ts → solid.test.ts} +0 -0
- /package/tests/{split-props-test.ts → split-props.test.ts} +0 -0
- /package/tests/{variants-test.ts → variants.test.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -30,28 +30,54 @@ import {
|
|
|
30
30
|
styleValueToJSXStyle,
|
|
31
31
|
} from "./utils.ts";
|
|
32
32
|
|
|
33
|
-
// Internal
|
|
33
|
+
// Internal compute path: pushes the variant classes contributed by this
|
|
34
|
+
// component (and its extends chain) into `classesOut` and merges any styles
|
|
35
|
+
// into `styleOut`. Base class is handled by callers via ComponentMeta.baseClass
|
|
36
|
+
// to avoid string-parsing round trips. Both outputs are mutated in place to
|
|
37
|
+
// avoid intermediate allocations.
|
|
38
|
+
type ComputeFn = (
|
|
39
|
+
resolved: Record<string, unknown>,
|
|
40
|
+
userVariantProps: Record<string, unknown>,
|
|
41
|
+
skipKeys: Set<string> | null,
|
|
42
|
+
skipValues: Record<string, Set<string>> | null,
|
|
43
|
+
classesOut: ClsxClassValue[],
|
|
44
|
+
styleOut: StyleValue,
|
|
45
|
+
) => void;
|
|
46
|
+
|
|
47
|
+
// Internal metadata stored on components but hidden from public types.
|
|
34
48
|
interface ComponentMeta {
|
|
35
49
|
baseClass: string;
|
|
36
50
|
staticDefaults: Record<string, unknown>;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
// Returns variants set via setDefaultVariants in the computed function chain.
|
|
52
|
+
// null when this component has no resolveDefaults work to do (no `computed`
|
|
53
|
+
// and no extends with work).
|
|
54
|
+
resolveDefaults:
|
|
55
|
+
| ((
|
|
56
|
+
childDefaults: Record<string, unknown>,
|
|
57
|
+
userProps?: Record<string, unknown>,
|
|
58
|
+
) => Record<string, unknown>)
|
|
59
|
+
| null;
|
|
60
|
+
// Returns variant classes + style for this component, used by extending
|
|
61
|
+
// components. Top-level rendering also routes through this.
|
|
62
|
+
compute: ComputeFn;
|
|
63
|
+
// Reference identity is used to detect mixed-factory `extend`. When a
|
|
64
|
+
// component is extended by a parent from a different `create()` call, the
|
|
65
|
+
// parent applies this transform to the extend's contribution before joining,
|
|
66
|
+
// preserving each factory's transform boundary.
|
|
67
|
+
transformClass: (className: string) => string;
|
|
41
68
|
}
|
|
42
69
|
|
|
43
70
|
const META_KEY = "__meta";
|
|
44
71
|
|
|
45
|
-
// Symbol property used to pass skip keys through the props object without
|
|
46
|
-
// polluting the actual variant values. This allows the computed function to
|
|
47
|
-
// see actual variant values while still skipping styling for overridden keys.
|
|
48
|
-
const SKIP_STYLE_KEYS = Symbol("skipStyleKeys");
|
|
49
|
-
const SKIP_STYLE_VARIANT_VALUES = Symbol("skipStyleVariantValues");
|
|
50
|
-
|
|
51
72
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
52
73
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
53
74
|
|
|
54
|
-
|
|
75
|
+
const EMPTY_DEFAULTS: Record<string, unknown> = Object.freeze({}) as Record<
|
|
76
|
+
string,
|
|
77
|
+
unknown
|
|
78
|
+
>;
|
|
79
|
+
|
|
80
|
+
// Dynamic property access on function requires cast through unknown.
|
|
55
81
|
function getComponentMeta(component: AnyComponent): ComponentMeta | undefined {
|
|
56
82
|
return (component as unknown as Record<string, unknown>)[META_KEY] as
|
|
57
83
|
| ComponentMeta
|
|
@@ -62,19 +88,6 @@ function setComponentMeta(component: AnyComponent, meta: ComponentMeta): void {
|
|
|
62
88
|
(component as unknown as Record<string, unknown>)[META_KEY] = meta;
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
/**
|
|
66
|
-
* Mutates target by assigning all properties from source. Avoids object spread
|
|
67
|
-
* overhead in hot paths where we're building up a result object.
|
|
68
|
-
*/
|
|
69
|
-
function assign<T extends object>(target: T, source: T): void {
|
|
70
|
-
for (const key in source) {
|
|
71
|
-
if (!hasOwn.call(source, key)) continue;
|
|
72
|
-
(target as Record<string, unknown>)[key] = (
|
|
73
|
-
source as Record<string, unknown>
|
|
74
|
-
)[key];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
91
|
export type {
|
|
79
92
|
ClassValue,
|
|
80
93
|
StyleValue,
|
|
@@ -280,28 +293,6 @@ function collectDisabledVariantValues(
|
|
|
280
293
|
return values;
|
|
281
294
|
}
|
|
282
295
|
|
|
283
|
-
/**
|
|
284
|
-
* Extracts classes from fullClass that are not in baseClass. Uses string
|
|
285
|
-
* comparison optimization: if fullClass starts with baseClass, just take the
|
|
286
|
-
* suffix.
|
|
287
|
-
*/
|
|
288
|
-
function extractVariantClasses(fullClass: string, baseClass: string): string {
|
|
289
|
-
if (!fullClass) return "";
|
|
290
|
-
if (!baseClass) return fullClass;
|
|
291
|
-
|
|
292
|
-
// Fast path: fullClass starts with baseClass (common case)
|
|
293
|
-
if (fullClass.startsWith(baseClass)) {
|
|
294
|
-
return fullClass.slice(baseClass.length).trim();
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Slow path: need to diff the class sets
|
|
298
|
-
const baseClassSet = new Set(baseClass.split(" ").filter(Boolean));
|
|
299
|
-
return fullClass
|
|
300
|
-
.split(" ")
|
|
301
|
-
.filter((c) => c && !baseClassSet.has(c))
|
|
302
|
-
.join(" ");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
296
|
interface NormalizedSource {
|
|
306
297
|
keys: string[];
|
|
307
298
|
variantKeys: string[];
|
|
@@ -352,7 +343,7 @@ function splitPropsImpl(
|
|
|
352
343
|
selfKeys: string[],
|
|
353
344
|
selfIsComponent: boolean,
|
|
354
345
|
props: Record<string, unknown>,
|
|
355
|
-
sources:
|
|
346
|
+
sources: unknown[],
|
|
356
347
|
): Record<string, unknown>[] {
|
|
357
348
|
const sourcesLength = sources.length;
|
|
358
349
|
const results: Record<string, unknown>[] = [];
|
|
@@ -373,8 +364,7 @@ function splitPropsImpl(
|
|
|
373
364
|
const effectiveKeyArrays: string[][] = [selfKeys];
|
|
374
365
|
|
|
375
366
|
for (let s = 0; s < sourcesLength; s++) {
|
|
376
|
-
const source = sources[s];
|
|
377
|
-
if (source === undefined) continue;
|
|
367
|
+
const source = normalizeKeySource(sources[s]);
|
|
378
368
|
const sourceResult: Record<string, unknown> = {};
|
|
379
369
|
|
|
380
370
|
const effectiveKeys =
|
|
@@ -431,16 +421,11 @@ export const splitProps: SplitPropsFunction = ((
|
|
|
431
421
|
...sources: unknown[]
|
|
432
422
|
) => {
|
|
433
423
|
const normalizedSource1 = normalizeKeySource(source1);
|
|
434
|
-
const sourcesLength = sources.length;
|
|
435
|
-
const normalizedSources: NormalizedSource[] = [];
|
|
436
|
-
for (let i = 0; i < sourcesLength; i++) {
|
|
437
|
-
normalizedSources.push(normalizeKeySource(sources[i]));
|
|
438
|
-
}
|
|
439
424
|
return splitPropsImpl(
|
|
440
425
|
normalizedSource1.keys,
|
|
441
426
|
normalizedSource1.isComponent,
|
|
442
427
|
props,
|
|
443
|
-
|
|
428
|
+
sources,
|
|
444
429
|
);
|
|
445
430
|
}) as SplitPropsFunction;
|
|
446
431
|
|
|
@@ -486,75 +471,6 @@ function buildPrebuiltVariant(variantDef: unknown): PrebuiltVariant {
|
|
|
486
471
|
};
|
|
487
472
|
}
|
|
488
473
|
|
|
489
|
-
/**
|
|
490
|
-
* Creates the resolveDefaults function for a component. This function returns
|
|
491
|
-
* only the variants set via setDefaultVariants in the computed function. Used
|
|
492
|
-
* by child components to get parent's computed defaults.
|
|
493
|
-
*/
|
|
494
|
-
function createResolveDefaults(
|
|
495
|
-
config: CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
496
|
-
staticDefaults: Record<string, unknown>,
|
|
497
|
-
): ComponentMeta["resolveDefaults"] {
|
|
498
|
-
const computed = config.computed;
|
|
499
|
-
const extend = config.extend;
|
|
500
|
-
return (childDefaults, userProps = {}) => {
|
|
501
|
-
// Merge: parent static < child static < user props
|
|
502
|
-
// This is what parent's computed will see in `variants`
|
|
503
|
-
const resolvedVariants: Record<string, unknown> = {};
|
|
504
|
-
Object.assign(resolvedVariants, staticDefaults);
|
|
505
|
-
for (const key in childDefaults) {
|
|
506
|
-
if (!hasOwn.call(childDefaults, key)) continue;
|
|
507
|
-
const v = childDefaults[key];
|
|
508
|
-
if (v === undefined) continue;
|
|
509
|
-
resolvedVariants[key] = v;
|
|
510
|
-
}
|
|
511
|
-
for (const key in userProps) {
|
|
512
|
-
if (!hasOwn.call(userProps, key)) continue;
|
|
513
|
-
const v = userProps[key];
|
|
514
|
-
if (v === undefined) continue;
|
|
515
|
-
resolvedVariants[key] = v;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Track which keys are set via setDefaultVariants
|
|
519
|
-
const computedDefaults: Record<string, unknown> = {};
|
|
520
|
-
|
|
521
|
-
// Propagate to extended components so their computed functions can run
|
|
522
|
-
if (extend) {
|
|
523
|
-
for (const ext of extend) {
|
|
524
|
-
const meta = getComponentMeta(ext);
|
|
525
|
-
if (!meta) continue;
|
|
526
|
-
const extDefaults = meta.resolveDefaults(childDefaults, userProps);
|
|
527
|
-
for (const k in extDefaults) {
|
|
528
|
-
if (hasOwn.call(extDefaults, k)) {
|
|
529
|
-
computedDefaults[k] = extDefaults[k];
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (computed) {
|
|
536
|
-
computed({
|
|
537
|
-
variants: resolvedVariants as VariantValues<Record<string, unknown>>,
|
|
538
|
-
setVariants: () => {},
|
|
539
|
-
setDefaultVariants: (newDefaults) => {
|
|
540
|
-
for (const key in newDefaults) {
|
|
541
|
-
if (!hasOwn.call(newDefaults, key)) continue;
|
|
542
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
543
|
-
if (userProps[key] !== undefined) continue;
|
|
544
|
-
if (isVariantDisabled(config, key)) continue;
|
|
545
|
-
if (isVariantValueDisabled(config, key, value)) continue;
|
|
546
|
-
computedDefaults[key] = value;
|
|
547
|
-
}
|
|
548
|
-
},
|
|
549
|
-
addClass: () => {},
|
|
550
|
-
addStyle: () => {},
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return computedDefaults;
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
|
|
558
474
|
/**
|
|
559
475
|
* Creates the cv and cx functions.
|
|
560
476
|
*/
|
|
@@ -574,11 +490,12 @@ export function create({
|
|
|
574
490
|
|
|
575
491
|
// ----- Pre-computed at creation time -----
|
|
576
492
|
const variantKeys = collectVariantKeys(config);
|
|
493
|
+
const variantKeysLength = variantKeys.length;
|
|
577
494
|
const disabledVariantKeys = collectDisabledVariantKeys(config);
|
|
578
495
|
const disabledVariantValues = collectDisabledVariantValues(config);
|
|
579
496
|
const hasDisabledVariantKeys = disabledVariantKeys.size > 0;
|
|
580
|
-
const
|
|
581
|
-
|
|
497
|
+
const disabledVariantValueKeys = Object.keys(disabledVariantValues);
|
|
498
|
+
const hasDisabledVariantValues = disabledVariantValueKeys.length > 0;
|
|
582
499
|
const hasAnyDisabled = hasDisabledVariantKeys || hasDisabledVariantValues;
|
|
583
500
|
|
|
584
501
|
const inputPropsKeys = ["class", "className", "style", ...variantKeys];
|
|
@@ -589,8 +506,7 @@ export function create({
|
|
|
589
506
|
const computedVariantsCfg = config.computedVariants;
|
|
590
507
|
const computed = config.computed;
|
|
591
508
|
const baseStyle = config.style;
|
|
592
|
-
const
|
|
593
|
-
config.class === undefined ? null : (config.class as ClassValue);
|
|
509
|
+
const hasBaseStyle = !!baseStyle;
|
|
594
510
|
|
|
595
511
|
// Pre-build variant entries for fast iteration. For each variant key in
|
|
596
512
|
// `variants`, we have a name and a PrebuiltVariant with normalized values.
|
|
@@ -670,22 +586,66 @@ export function create({
|
|
|
670
586
|
}
|
|
671
587
|
|
|
672
588
|
// Pre-build extended component info, so we don't have to call
|
|
673
|
-
// `getComponentMeta` per render.
|
|
674
|
-
|
|
589
|
+
// `getComponentMeta` per render. Extends from a different `create()`
|
|
590
|
+
// factory (different `transformClass` identity) need their contribution
|
|
591
|
+
// transformed by their own `transformClass` before being joined into our
|
|
592
|
+
// class string — otherwise our outer `transformClass(clsx(allClasses))`
|
|
593
|
+
// would be the only transform that runs, and the extend's factory would
|
|
594
|
+
// be silently bypassed for any base coming from `extend: [otherFactoryCv]`.
|
|
595
|
+
const extMetas: ComponentMeta[] = [];
|
|
675
596
|
const extBaseClassesArr: string[] = [];
|
|
676
|
-
const
|
|
597
|
+
const extIsolated: boolean[] = [];
|
|
598
|
+
let hasIsolatedExt = false;
|
|
677
599
|
if (extend) {
|
|
678
600
|
for (const ext of extend) {
|
|
679
601
|
const meta = getComponentMeta(ext);
|
|
602
|
+
if (!meta) continue;
|
|
680
603
|
extMetas.push(meta);
|
|
681
|
-
|
|
604
|
+
const isolated = meta.transformClass !== transformClass;
|
|
605
|
+
extIsolated.push(isolated);
|
|
606
|
+
if (isolated) {
|
|
607
|
+
hasIsolatedExt = true;
|
|
608
|
+
// Apply the extend's own transformClass to its base class so it
|
|
609
|
+
// survives our outer transform (which still applies on top, matching
|
|
610
|
+
// the original public-component round-trip behavior).
|
|
611
|
+
extBaseClassesArr.push(meta.transformClass(meta.baseClass));
|
|
612
|
+
} else {
|
|
613
|
+
extBaseClassesArr.push(meta.baseClass);
|
|
614
|
+
}
|
|
682
615
|
}
|
|
683
616
|
}
|
|
684
|
-
const extCount =
|
|
685
|
-
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
617
|
+
const extCount = extMetas.length;
|
|
618
|
+
|
|
619
|
+
// Filter to only extends whose `resolveDefaults` actually does work
|
|
620
|
+
// (config.computed exists, transitively). Iterating these in
|
|
621
|
+
// `resolveVariantsHot` skips empty work.
|
|
622
|
+
const extMetasWithResolveDefaults: ComponentMeta[] = [];
|
|
623
|
+
for (let i = 0; i < extCount; i++) {
|
|
624
|
+
if (extMetas[i].resolveDefaults) {
|
|
625
|
+
extMetasWithResolveDefaults.push(extMetas[i]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const extMetasWithResolveDefaultsCount = extMetasWithResolveDefaults.length;
|
|
629
|
+
|
|
630
|
+
// Pre-compute static skip key/value sets to pass to extends. These never
|
|
631
|
+
// change across calls — when caller passes no skip sets, we reuse the same
|
|
632
|
+
// object and avoid Set allocation.
|
|
633
|
+
let staticExtSkipKeys: Set<string> | null = null;
|
|
634
|
+
if (hasDisabledVariantKeys || computedVariantCount > 0) {
|
|
635
|
+
staticExtSkipKeys = new Set<string>();
|
|
636
|
+
for (const k of disabledVariantKeys) staticExtSkipKeys.add(k);
|
|
637
|
+
for (let i = 0; i < computedVariantCount; i++) {
|
|
638
|
+
staticExtSkipKeys.add(computedVariantNames[i]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// Skip values are passed directly to extends. We can reuse the same object
|
|
642
|
+
// when no caller-provided values need merging.
|
|
643
|
+
const staticExtSkipValues: Record<string, Set<string>> | null =
|
|
644
|
+
hasDisabledVariantValues ? disabledVariantValues : null;
|
|
645
|
+
|
|
646
|
+
// Branches on `hasAnyDisabled` so the no-disabled path skips the per-key
|
|
647
|
+
// filter checks entirely — most components have no disabled variants and
|
|
648
|
+
// hit only the plain copy.
|
|
689
649
|
function filterDisabledInto(
|
|
690
650
|
input: Record<string, unknown>,
|
|
691
651
|
out: Record<string, unknown>,
|
|
@@ -710,12 +670,82 @@ export function create({
|
|
|
710
670
|
}
|
|
711
671
|
}
|
|
712
672
|
|
|
713
|
-
// Pre-create
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
|
|
673
|
+
// Pre-create resolveDefaults function — used by parents during their
|
|
674
|
+
// `resolveVariantsHot`. Returns the variants set via setDefaultVariants in
|
|
675
|
+
// the computed function chain.
|
|
676
|
+
//
|
|
677
|
+
// When this component has no `computed` and no `extend` with work, the
|
|
678
|
+
// function is null — callers can skip iterating it entirely.
|
|
679
|
+
const resolveDefaultsFn: ComponentMeta["resolveDefaults"] =
|
|
680
|
+
computed || extMetasWithResolveDefaultsCount > 0
|
|
681
|
+
? (
|
|
682
|
+
childDefaults: Record<string, unknown>,
|
|
683
|
+
userProps: Record<string, unknown> = EMPTY_DEFAULTS,
|
|
684
|
+
) => {
|
|
685
|
+
// userProps is contractually variant-only (callers pre-filter
|
|
686
|
+
// when starting from a full props object).
|
|
687
|
+
const resolvedVariants: Record<string, unknown> = {};
|
|
688
|
+
Object.assign(resolvedVariants, staticDefaults);
|
|
689
|
+
for (const key in childDefaults) {
|
|
690
|
+
if (!hasOwn.call(childDefaults, key)) continue;
|
|
691
|
+
const v = childDefaults[key];
|
|
692
|
+
if (v === undefined) continue;
|
|
693
|
+
resolvedVariants[key] = v;
|
|
694
|
+
}
|
|
695
|
+
for (const key in userProps) {
|
|
696
|
+
if (!hasOwn.call(userProps, key)) continue;
|
|
697
|
+
const v = userProps[key];
|
|
698
|
+
if (v === undefined) continue;
|
|
699
|
+
resolvedVariants[key] = v;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const computedDefaults: Record<string, unknown> = {};
|
|
703
|
+
|
|
704
|
+
for (let i = 0; i < extMetasWithResolveDefaultsCount; i++) {
|
|
705
|
+
const extDefaults = extMetasWithResolveDefaults[i]
|
|
706
|
+
.resolveDefaults!(childDefaults, userProps);
|
|
707
|
+
for (const k in extDefaults) {
|
|
708
|
+
if (!hasOwn.call(extDefaults, k)) continue;
|
|
709
|
+
computedDefaults[k] = extDefaults[k];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (computed) {
|
|
714
|
+
// Filter to own variant keys so `computed.ctx.variants` matches
|
|
715
|
+
// `VariantValues<V>` when this component is used as an extend by
|
|
716
|
+
// a parent that adds extra variant keys (those keys would
|
|
717
|
+
// otherwise leak through `userProps`).
|
|
718
|
+
const ownVariants: Record<string, unknown> = {};
|
|
719
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
720
|
+
const k = variantKeys[i];
|
|
721
|
+
if (hasOwn.call(resolvedVariants, k)) {
|
|
722
|
+
ownVariants[k] = resolvedVariants[k];
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
computed({
|
|
726
|
+
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
727
|
+
setVariants: noop,
|
|
728
|
+
setDefaultVariants: (newDefaults) => {
|
|
729
|
+
for (const key in newDefaults) {
|
|
730
|
+
if (!hasOwn.call(newDefaults, key)) continue;
|
|
731
|
+
const value = (newDefaults as Record<string, unknown>)[key];
|
|
732
|
+
if (userProps[key] !== undefined) continue;
|
|
733
|
+
if (isVariantDisabled(config, key)) continue;
|
|
734
|
+
if (isVariantValueDisabled(config, key, value)) continue;
|
|
735
|
+
computedDefaults[key] = value;
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
addClass: noop,
|
|
739
|
+
addStyle: noop,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
717
742
|
|
|
718
|
-
|
|
743
|
+
return computedDefaults;
|
|
744
|
+
}
|
|
745
|
+
: null;
|
|
746
|
+
|
|
747
|
+
// Hot path: resolve variants by merging static defaults + extends'
|
|
748
|
+
// computed defaults + user-provided props.
|
|
719
749
|
function resolveVariantsHot(
|
|
720
750
|
propsVariants: Record<string, unknown>,
|
|
721
751
|
): Record<string, unknown> {
|
|
@@ -723,22 +753,20 @@ export function create({
|
|
|
723
753
|
const defaults: Record<string, unknown> = {};
|
|
724
754
|
Object.assign(defaults, staticDefaults);
|
|
725
755
|
|
|
726
|
-
// Apply computed defaults from extended components
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
defaults[k] = extComputed[k];
|
|
735
|
-
}
|
|
736
|
-
}
|
|
756
|
+
// Apply computed defaults from extended components (only those that have
|
|
757
|
+
// actual work to do).
|
|
758
|
+
for (let i = 0; i < extMetasWithResolveDefaultsCount; i++) {
|
|
759
|
+
const meta = extMetasWithResolveDefaults[i];
|
|
760
|
+
const extComputed = meta.resolveDefaults!(defaults, propsVariants);
|
|
761
|
+
for (const k in extComputed) {
|
|
762
|
+
if (!hasOwn.call(extComputed, k)) continue;
|
|
763
|
+
defaults[k] = extComputed[k];
|
|
737
764
|
}
|
|
738
765
|
}
|
|
739
766
|
|
|
740
|
-
//
|
|
741
|
-
//
|
|
767
|
+
// Apply propsVariants on top (filter undefined). propsVariants is
|
|
768
|
+
// contractually variant-only here — callers building from a full props
|
|
769
|
+
// object filter to variant keys before calling.
|
|
742
770
|
for (const k in propsVariants) {
|
|
743
771
|
if (!hasOwn.call(propsVariants, k)) continue;
|
|
744
772
|
const v = propsVariants[k];
|
|
@@ -746,239 +774,231 @@ export function create({
|
|
|
746
774
|
defaults[k] = v;
|
|
747
775
|
}
|
|
748
776
|
|
|
777
|
+
if (!hasAnyDisabled) return defaults;
|
|
778
|
+
|
|
749
779
|
// Filter disabled
|
|
750
780
|
const result: Record<string, unknown> = {};
|
|
751
781
|
filterDisabledInto(defaults, result);
|
|
752
782
|
return result;
|
|
753
783
|
}
|
|
754
784
|
|
|
755
|
-
//
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (key in props) {
|
|
773
|
-
variantProps[key] = (props as Record<string, unknown>)[key];
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Resolve variants with defaults
|
|
778
|
-
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
779
|
-
|
|
780
|
-
// Run computed function (may update variants and emit class/style)
|
|
781
|
-
let computedClassesArr: ClassValue[] | null = null;
|
|
782
|
-
let computedStyleObj: StyleValue | null = null;
|
|
785
|
+
// Core compute path. Called both for top-level rendering (via
|
|
786
|
+
// `computeResult`) and recursively when this component is used as an
|
|
787
|
+
// `extend` target by another component. Pushes variant classes (excluding
|
|
788
|
+
// base class) into `classesOut` and merges styles into `styleOut`.
|
|
789
|
+
const compute: ComputeFn = (
|
|
790
|
+
resolved,
|
|
791
|
+
userVariantProps,
|
|
792
|
+
skipKeys,
|
|
793
|
+
skipValues,
|
|
794
|
+
classesOut,
|
|
795
|
+
styleOut,
|
|
796
|
+
) => {
|
|
797
|
+
// Run `computed` (if any). May modify resolved variants and emit classes
|
|
798
|
+
// and styles.
|
|
799
|
+
let workingResolved = resolved;
|
|
800
|
+
let cClasses: ClassValue[] | null = null;
|
|
801
|
+
let cStyle: StyleValue | null = null;
|
|
783
802
|
|
|
784
803
|
if (computed) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
804
|
+
// When this component is being extended, `resolved` is the parent's
|
|
805
|
+
// workingResolved (a superset of our variant keys). Filter to our own
|
|
806
|
+
// keys for `ctx.variants` so the user's `computed` callback sees the
|
|
807
|
+
// shape declared by `VariantValues<V>` and not foreign parent keys.
|
|
808
|
+
const ownVariants: Record<string, unknown> = {};
|
|
809
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
810
|
+
const k = variantKeys[i];
|
|
811
|
+
if (hasOwn.call(resolved, k)) ownVariants[k] = resolved[k];
|
|
812
|
+
}
|
|
813
|
+
// Lazy-init updatedVariants — many computeds only inspect `variants`
|
|
814
|
+
// or call setDefaultVariants for keys the user already set, so the
|
|
815
|
+
// copy is unnecessary in the common case.
|
|
816
|
+
let updatedVariants: Record<string, unknown> | null = null;
|
|
817
|
+
const localCClasses: ClassValue[] = [];
|
|
818
|
+
let localCStyle: StyleValue | null = null;
|
|
819
|
+
const ensureUpdated = (): Record<string, unknown> => {
|
|
820
|
+
if (updatedVariants) return updatedVariants;
|
|
821
|
+
const u: Record<string, unknown> = {};
|
|
822
|
+
Object.assign(u, ownVariants);
|
|
823
|
+
updatedVariants = u;
|
|
824
|
+
return u;
|
|
825
|
+
};
|
|
789
826
|
const ctx = {
|
|
790
|
-
variants:
|
|
827
|
+
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
791
828
|
setVariants: (
|
|
792
829
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
793
830
|
) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
)
|
|
799
|
-
|
|
831
|
+
if (!hasAnyDisabled) {
|
|
832
|
+
Object.assign(ensureUpdated(), newVariants);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
for (const key in newVariants) {
|
|
836
|
+
if (!hasOwn.call(newVariants, key)) continue;
|
|
837
|
+
if (disabledVariantKeys.has(key)) continue;
|
|
838
|
+
const value = (newVariants as Record<string, unknown>)[key];
|
|
839
|
+
if (hasDisabledVariantValues) {
|
|
840
|
+
const valueKey = getVariantValueKey(value);
|
|
841
|
+
if (
|
|
842
|
+
valueKey != null &&
|
|
843
|
+
disabledVariantValues[key]?.has(valueKey)
|
|
844
|
+
) {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
ensureUpdated()[key] = value;
|
|
849
|
+
}
|
|
800
850
|
},
|
|
801
851
|
setDefaultVariants: (
|
|
802
852
|
newDefaults: VariantValues<Record<string, unknown>>,
|
|
803
853
|
) => {
|
|
804
854
|
for (const key in newDefaults) {
|
|
805
855
|
if (!hasOwn.call(newDefaults, key)) continue;
|
|
806
|
-
if (
|
|
807
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
856
|
+
if (userVariantProps[key] !== undefined) continue;
|
|
808
857
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
valueKey
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
858
|
+
if (hasAnyDisabled) {
|
|
859
|
+
if (disabledVariantKeys.has(key)) continue;
|
|
860
|
+
const valueKey = getVariantValueKey(value);
|
|
861
|
+
if (
|
|
862
|
+
valueKey != null &&
|
|
863
|
+
disabledVariantValues[key]?.has(valueKey)
|
|
864
|
+
) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
815
867
|
}
|
|
816
|
-
|
|
868
|
+
ensureUpdated()[key] = value;
|
|
817
869
|
}
|
|
818
870
|
},
|
|
819
871
|
addClass: (className: ClassValue) => {
|
|
820
|
-
|
|
872
|
+
localCClasses.push(className);
|
|
821
873
|
},
|
|
822
874
|
addStyle: (newStyle: StyleValue) => {
|
|
823
|
-
if (!
|
|
824
|
-
assign(
|
|
875
|
+
if (!localCStyle) localCStyle = {};
|
|
876
|
+
Object.assign(localCStyle, newStyle);
|
|
825
877
|
},
|
|
826
878
|
};
|
|
827
879
|
const result = computed(ctx);
|
|
828
880
|
if (result != null) {
|
|
829
881
|
const r = extractClassAndStylePrebuilt(result);
|
|
830
|
-
if (r.class != null)
|
|
882
|
+
if (r.class != null) localCClasses.push(r.class);
|
|
831
883
|
if (r.style) {
|
|
832
|
-
if (!
|
|
833
|
-
assign(
|
|
884
|
+
if (!localCStyle) localCStyle = {};
|
|
885
|
+
Object.assign(localCStyle, r.style);
|
|
834
886
|
}
|
|
835
887
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
// component. Only allocate when needed.
|
|
846
|
-
const hasSkipKeys = !!skipStyleKeysIn || disabledVariantKeys.size > 0;
|
|
847
|
-
let currentVariantKeys: Set<string> | null = null;
|
|
848
|
-
if (hasSkipKeys) {
|
|
849
|
-
currentVariantKeys = new Set<string>();
|
|
850
|
-
if (skipStyleKeysIn) {
|
|
851
|
-
for (const k of skipStyleKeysIn) currentVariantKeys.add(k);
|
|
852
|
-
}
|
|
853
|
-
for (const k of disabledVariantKeys) currentVariantKeys.add(k);
|
|
854
|
-
}
|
|
855
|
-
// computedVariantKeys is currentVariantKeys + computedVariants names
|
|
856
|
-
let computedVariantKeysSet: Set<string> | null = null;
|
|
857
|
-
if (hasExtend) {
|
|
858
|
-
if (currentVariantKeys || computedVariantNames.length > 0) {
|
|
859
|
-
computedVariantKeysSet = new Set<string>();
|
|
860
|
-
if (currentVariantKeys) {
|
|
861
|
-
for (const k of currentVariantKeys) computedVariantKeysSet.add(k);
|
|
862
|
-
}
|
|
863
|
-
for (let i = 0; i < computedVariantNames.length; i++) {
|
|
864
|
-
computedVariantKeysSet.add(computedVariantNames[i]);
|
|
888
|
+
cClasses = localCClasses;
|
|
889
|
+
cStyle = localCStyle;
|
|
890
|
+
if (updatedVariants) {
|
|
891
|
+
if (hasAnyDisabled) {
|
|
892
|
+
const filteredUpdated: Record<string, unknown> = {};
|
|
893
|
+
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
894
|
+
workingResolved = filteredUpdated;
|
|
895
|
+
} else {
|
|
896
|
+
workingResolved = updatedVariants;
|
|
865
897
|
}
|
|
866
898
|
}
|
|
867
899
|
}
|
|
868
900
|
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
901
|
+
// Build skip sets to pass to extends. Reuse precomputed values when no
|
|
902
|
+
// caller-provided sets need merging.
|
|
903
|
+
let extSkipKeys: Set<string> | null;
|
|
904
|
+
if (skipKeys === null) {
|
|
905
|
+
extSkipKeys = staticExtSkipKeys;
|
|
906
|
+
} else if (staticExtSkipKeys === null) {
|
|
907
|
+
extSkipKeys = skipKeys;
|
|
908
|
+
} else {
|
|
909
|
+
extSkipKeys = new Set(skipKeys);
|
|
910
|
+
for (const k of staticExtSkipKeys) extSkipKeys.add(k);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
let extSkipVals: Record<string, Set<string>> | null;
|
|
914
|
+
if (skipValues === null) {
|
|
915
|
+
extSkipVals = staticExtSkipValues;
|
|
916
|
+
} else if (staticExtSkipValues === null) {
|
|
917
|
+
extSkipVals = skipValues;
|
|
918
|
+
} else {
|
|
919
|
+
extSkipVals = {};
|
|
920
|
+
for (const k in skipValues) {
|
|
921
|
+
extSkipVals[k] = skipValues[k];
|
|
885
922
|
}
|
|
886
|
-
for (
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
923
|
+
for (const k in staticExtSkipValues) {
|
|
924
|
+
const existing = extSkipVals[k];
|
|
925
|
+
if (existing) {
|
|
926
|
+
const merged = new Set<string>(existing);
|
|
927
|
+
for (const v of staticExtSkipValues[k]) merged.add(v);
|
|
928
|
+
extSkipVals[k] = merged;
|
|
929
|
+
} else {
|
|
930
|
+
extSkipVals[k] = staticExtSkipValues[k];
|
|
892
931
|
}
|
|
893
|
-
for (const v of disabledVariantValues[k]) bucket.add(v);
|
|
894
932
|
}
|
|
895
933
|
}
|
|
896
934
|
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
//
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
//
|
|
935
|
+
// Run extends' contributions first (their full classes + styles) so our
|
|
936
|
+
// own base style and variants apply on top, matching the original
|
|
937
|
+
// ext1 → ext2 → … → current ordering.
|
|
938
|
+
//
|
|
939
|
+
// `workingResolved` is passed as the extends' `userVariantProps`. This
|
|
940
|
+
// is deliberate — by the time `compute` runs, the resolveDefaults chain
|
|
941
|
+
// and our own `computed`'s `setDefaultVariants` have already produced
|
|
942
|
+
// the most-specific resolution for every key. Treating those values as
|
|
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.
|
|
909
950
|
if (hasExtend) {
|
|
910
|
-
const hasComputedVariantKeysSet =
|
|
911
|
-
!!computedVariantKeysSet && computedVariantKeysSet.size > 0;
|
|
912
|
-
const hasComputedVariantValues =
|
|
913
|
-
!!computedVariantValues &&
|
|
914
|
-
Object.keys(computedVariantValues).length > 0;
|
|
915
|
-
const hasSkipForExt =
|
|
916
|
-
hasComputedVariantKeysSet || hasComputedVariantValues;
|
|
917
|
-
|
|
918
|
-
const extVariantClasses: ClassValue[] = [];
|
|
919
|
-
|
|
920
951
|
for (let i = 0; i < extCount; i++) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
952
|
+
if (hasIsolatedExt && extIsolated[i]) {
|
|
953
|
+
// Isolated extend (different factory): gather its variant classes
|
|
954
|
+
// into a scratch array, then push the joined string after applying
|
|
955
|
+
// its own transformClass. Our outer transform applies on top.
|
|
956
|
+
const extClasses: ClsxClassValue[] = [];
|
|
957
|
+
extMetas[i].compute(
|
|
958
|
+
workingResolved,
|
|
959
|
+
workingResolved,
|
|
960
|
+
extSkipKeys,
|
|
961
|
+
extSkipVals,
|
|
962
|
+
extClasses,
|
|
963
|
+
styleOut,
|
|
964
|
+
);
|
|
965
|
+
if (extClasses.length > 0) {
|
|
966
|
+
const joined = clsx(extClasses);
|
|
967
|
+
if (joined.length > 0) {
|
|
968
|
+
classesOut.push(
|
|
969
|
+
extMetas[i].transformClass(joined) as ClsxClassValue,
|
|
970
|
+
);
|
|
930
971
|
}
|
|
931
972
|
}
|
|
932
|
-
if (hasComputedVariantKeysSet) {
|
|
933
|
-
propsForExt[SKIP_STYLE_KEYS] = computedVariantKeysSet;
|
|
934
|
-
}
|
|
935
|
-
if (hasComputedVariantValues) {
|
|
936
|
-
propsForExt[SKIP_STYLE_VARIANT_VALUES] = computedVariantValues;
|
|
937
|
-
}
|
|
938
973
|
} else {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
if (extResult.style != null) {
|
|
948
|
-
assign(allStyle, normalizeStyle(extResult.style));
|
|
974
|
+
extMetas[i].compute(
|
|
975
|
+
workingResolved,
|
|
976
|
+
workingResolved,
|
|
977
|
+
extSkipKeys,
|
|
978
|
+
extSkipVals,
|
|
979
|
+
classesOut,
|
|
980
|
+
styleOut,
|
|
981
|
+
);
|
|
949
982
|
}
|
|
950
|
-
|
|
951
|
-
allClasses.push(extBaseClass);
|
|
952
|
-
const fullClass =
|
|
953
|
-
"className" in extResult ? extResult.className : extResult.class;
|
|
954
|
-
const variantPortion = extractVariantClasses(fullClass, extBaseClass);
|
|
955
|
-
if (variantPortion) extVariantClasses.push(variantPortion);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// 2. Current base class
|
|
959
|
-
allClasses.push(baseClass);
|
|
960
|
-
if (baseStyle) assign(allStyle, baseStyle);
|
|
961
|
-
|
|
962
|
-
// 4. Extended variant classes
|
|
963
|
-
for (let i = 0; i < extVariantClasses.length; i++) {
|
|
964
|
-
allClasses.push(extVariantClasses[i]);
|
|
965
983
|
}
|
|
966
|
-
} else {
|
|
967
|
-
// No extends: just current base
|
|
968
|
-
allClasses.push(baseClass);
|
|
969
|
-
if (baseStyle) assign(allStyle, baseStyle);
|
|
970
984
|
}
|
|
971
985
|
|
|
972
|
-
//
|
|
973
|
-
|
|
986
|
+
// Apply own base style (after extends' styles, matching original order).
|
|
987
|
+
if (hasBaseStyle) Object.assign(styleOut, baseStyle);
|
|
988
|
+
|
|
989
|
+
// Apply own variants. Skip keys/values come from caller (e.g., parent
|
|
990
|
+
// wants its own computedVariants to override this variant).
|
|
991
|
+
// `variantEntryNames` already excludes disabled keys (those with `null`
|
|
992
|
+
// value in config), so we don't re-check `disabledVariantKeys` here.
|
|
993
|
+
const ownSkipKeys = skipKeys;
|
|
994
|
+
const ownSkipValues = skipValues;
|
|
974
995
|
for (let i = 0; i < variantEntryCount; i++) {
|
|
975
996
|
const variantName = variantEntryNames[i];
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const selectedValue = resolvedVariants[variantName];
|
|
997
|
+
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
998
|
+
const selectedValue = workingResolved[variantName];
|
|
979
999
|
if (selectedValue === undefined) continue;
|
|
980
1000
|
const selectedKey = getVariantValueKey(selectedValue);
|
|
981
|
-
|
|
1001
|
+
const variant = variantEntryDefs[i];
|
|
982
1002
|
if (
|
|
983
1003
|
variant.disabledValues &&
|
|
984
1004
|
selectedKey != null &&
|
|
@@ -986,13 +1006,10 @@ export function create({
|
|
|
986
1006
|
) {
|
|
987
1007
|
continue;
|
|
988
1008
|
}
|
|
989
|
-
// skipVariantValues comes from skipStyleVariantValuesIn (only relevant
|
|
990
|
-
// if this is being called as an extended component). For top-level it
|
|
991
|
-
// would be undefined.
|
|
992
1009
|
if (
|
|
993
|
-
|
|
1010
|
+
ownSkipValues &&
|
|
994
1011
|
selectedKey != null &&
|
|
995
|
-
|
|
1012
|
+
ownSkipValues[variantName]?.has(selectedKey)
|
|
996
1013
|
) {
|
|
997
1014
|
continue;
|
|
998
1015
|
}
|
|
@@ -1001,83 +1018,149 @@ export function create({
|
|
|
1001
1018
|
if (selectedKey == null) continue;
|
|
1002
1019
|
const v = variant.values[selectedKey];
|
|
1003
1020
|
if (!v) continue;
|
|
1004
|
-
if (v.class != null)
|
|
1005
|
-
if (v.style) assign(
|
|
1006
|
-
} else if (variant.shorthand) {
|
|
1007
|
-
|
|
1008
|
-
if (
|
|
1009
|
-
|
|
1010
|
-
if (v.class != null) allClasses.push(v.class);
|
|
1011
|
-
if (v.style) assign(allStyle, v.style);
|
|
1012
|
-
}
|
|
1021
|
+
if (v.class != null) classesOut.push(v.class as ClsxClassValue);
|
|
1022
|
+
if (v.style) Object.assign(styleOut, v.style);
|
|
1023
|
+
} else if (variant.shorthand && selectedValue === true) {
|
|
1024
|
+
const v = variant.shorthand;
|
|
1025
|
+
if (v.class != null) classesOut.push(v.class as ClsxClassValue);
|
|
1026
|
+
if (v.style) Object.assign(styleOut, v.style);
|
|
1013
1027
|
}
|
|
1014
1028
|
}
|
|
1015
1029
|
|
|
1016
|
-
// computedVariants
|
|
1030
|
+
// Apply computedVariants.
|
|
1017
1031
|
for (let i = 0; i < computedVariantCount; i++) {
|
|
1018
1032
|
const variantName = computedVariantNames[i];
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const selectedValue = resolvedVariants[variantName];
|
|
1033
|
+
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
1034
|
+
const selectedValue = workingResolved[variantName];
|
|
1022
1035
|
if (selectedValue === undefined) continue;
|
|
1023
1036
|
const selectedKey = getVariantValueKey(selectedValue);
|
|
1024
1037
|
if (
|
|
1025
|
-
|
|
1038
|
+
ownSkipValues &&
|
|
1026
1039
|
selectedKey != null &&
|
|
1027
|
-
|
|
1040
|
+
ownSkipValues[variantName]?.has(selectedKey)
|
|
1028
1041
|
) {
|
|
1029
1042
|
continue;
|
|
1030
1043
|
}
|
|
1044
|
+
const fn = computedVariantFns[i];
|
|
1031
1045
|
const computedResult = fn(selectedValue);
|
|
1032
1046
|
if (computedResult == null) continue;
|
|
1033
1047
|
const r = extractClassAndStylePrebuilt(computedResult);
|
|
1034
|
-
if (r.class != null)
|
|
1035
|
-
if (r.style) assign(
|
|
1048
|
+
if (r.class != null) classesOut.push(r.class as ClsxClassValue);
|
|
1049
|
+
if (r.style) Object.assign(styleOut, r.style);
|
|
1036
1050
|
}
|
|
1037
1051
|
|
|
1038
|
-
// computed
|
|
1039
|
-
if (
|
|
1040
|
-
for (let i = 0; i <
|
|
1041
|
-
|
|
1052
|
+
// Apply `computed` results — must come after own variants/computedVariants.
|
|
1053
|
+
if (cClasses) {
|
|
1054
|
+
for (let i = 0; i < cClasses.length; i++) {
|
|
1055
|
+
classesOut.push(cClasses[i] as ClsxClassValue);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (cStyle) Object.assign(styleOut, cStyle);
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// Top-level: resolves variants from user props, calls compute, then
|
|
1062
|
+
// assembles className and style with user-provided class/style overrides.
|
|
1063
|
+
const computeResult = (
|
|
1064
|
+
props: ComponentProps<MergedVariants> = EMPTY_DEFAULTS as ComponentProps<MergedVariants>,
|
|
1065
|
+
): { className: string; style: StyleValue } => {
|
|
1066
|
+
const propsRecord = props as Record<string, unknown>;
|
|
1067
|
+
|
|
1068
|
+
// Inline resolve: avoids allocating a separate variantProps object for
|
|
1069
|
+
// the common case where no extends need a resolveDefaults pass.
|
|
1070
|
+
// resolveVariantsHot would also work here but assumes its input is
|
|
1071
|
+
// variant-only (it uses for-in for speed).
|
|
1072
|
+
let resolved: Record<string, unknown> = {};
|
|
1073
|
+
Object.assign(resolved, staticDefaults);
|
|
1074
|
+
|
|
1075
|
+
let userVariantProps: Record<string, unknown>;
|
|
1076
|
+
if (extMetasWithResolveDefaultsCount > 0) {
|
|
1077
|
+
// Some extends need a resolveDefaults pass. They expect a variant-only
|
|
1078
|
+
// object as `userProps`, so we extract one.
|
|
1079
|
+
const variantProps: Record<string, unknown> = {};
|
|
1080
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
1081
|
+
const key = variantKeys[i];
|
|
1082
|
+
if (hasOwn.call(propsRecord, key)) {
|
|
1083
|
+
variantProps[key] = propsRecord[key];
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
for (let i = 0; i < extMetasWithResolveDefaultsCount; i++) {
|
|
1087
|
+
const meta = extMetasWithResolveDefaults[i];
|
|
1088
|
+
const extComputed = meta.resolveDefaults!(resolved, variantProps);
|
|
1089
|
+
for (const k in extComputed) {
|
|
1090
|
+
if (!hasOwn.call(extComputed, k)) continue;
|
|
1091
|
+
resolved[k] = extComputed[k];
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
for (const k in variantProps) {
|
|
1095
|
+
if (!hasOwn.call(variantProps, k)) continue;
|
|
1096
|
+
const v = variantProps[k];
|
|
1097
|
+
if (v === undefined) continue;
|
|
1098
|
+
resolved[k] = v;
|
|
1042
1099
|
}
|
|
1100
|
+
userVariantProps = variantProps;
|
|
1101
|
+
} else {
|
|
1102
|
+
// Fast path: walk variantKeys directly against propsRecord. Use
|
|
1103
|
+
// hasOwn so a polluted Object.prototype can't introduce variant
|
|
1104
|
+
// values the user didn't pass.
|
|
1105
|
+
for (let i = 0; i < variantKeysLength; i++) {
|
|
1106
|
+
const key = variantKeys[i];
|
|
1107
|
+
if (!hasOwn.call(propsRecord, key)) continue;
|
|
1108
|
+
const v = propsRecord[key];
|
|
1109
|
+
if (v === undefined) continue;
|
|
1110
|
+
resolved[key] = v;
|
|
1111
|
+
}
|
|
1112
|
+
userVariantProps = propsRecord;
|
|
1043
1113
|
}
|
|
1044
|
-
if (computedStyleObj) assign(allStyle, computedStyleObj);
|
|
1045
1114
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1115
|
+
if (hasAnyDisabled) {
|
|
1116
|
+
const filtered: Record<string, unknown> = {};
|
|
1117
|
+
filterDisabledInto(resolved, filtered);
|
|
1118
|
+
resolved = filtered;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Build allClasses directly. computedBaseClass already has all extend
|
|
1122
|
+
// bases joined with config.class — `compute` only adds variant classes
|
|
1123
|
+
// on top.
|
|
1124
|
+
const allClasses: ClsxClassValue[] = [computedBaseClass];
|
|
1125
|
+
const style: StyleValue = {};
|
|
1126
|
+
compute(resolved, userVariantProps, null, null, allClasses, style);
|
|
1127
|
+
|
|
1128
|
+
// Apply user-provided class / className.
|
|
1129
|
+
if ("class" in propsRecord) {
|
|
1130
|
+
allClasses.push(propsRecord.class as ClsxClassValue);
|
|
1131
|
+
}
|
|
1132
|
+
if ("className" in propsRecord) {
|
|
1133
|
+
allClasses.push(propsRecord.className as ClsxClassValue);
|
|
1134
|
+
}
|
|
1051
1135
|
|
|
1052
|
-
//
|
|
1053
|
-
const psv =
|
|
1136
|
+
// Apply user-provided style.
|
|
1137
|
+
const psv = propsRecord.style;
|
|
1054
1138
|
if (psv != null) {
|
|
1055
|
-
// Fast path: if it's an object with no keys, skip
|
|
1056
1139
|
if (typeof psv === "string") {
|
|
1057
1140
|
if (psv.length > 0) {
|
|
1058
|
-
assign(
|
|
1141
|
+
Object.assign(style, htmlStyleToStyleValue(psv));
|
|
1059
1142
|
}
|
|
1060
1143
|
} else if (typeof psv === "object") {
|
|
1061
|
-
//
|
|
1144
|
+
// Don't allocate when empty.
|
|
1062
1145
|
let hasAnyKey = false;
|
|
1063
1146
|
for (const _ in psv) {
|
|
1064
1147
|
hasAnyKey = true;
|
|
1065
1148
|
break;
|
|
1066
1149
|
}
|
|
1067
1150
|
if (hasAnyKey) {
|
|
1068
|
-
assign(
|
|
1151
|
+
Object.assign(style, normalizeStyle(psv));
|
|
1069
1152
|
}
|
|
1070
1153
|
}
|
|
1071
1154
|
}
|
|
1072
1155
|
|
|
1073
1156
|
return {
|
|
1074
|
-
className:
|
|
1075
|
-
style
|
|
1157
|
+
className: transformClass(clsx(allClasses)),
|
|
1158
|
+
style,
|
|
1076
1159
|
};
|
|
1077
1160
|
};
|
|
1078
1161
|
|
|
1079
1162
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1080
|
-
const variantProps =
|
|
1163
|
+
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1081
1164
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1082
1165
|
// Run computed function to get variants set via setVariants and
|
|
1083
1166
|
// setDefaultVariants
|
|
@@ -1089,12 +1172,25 @@ export function create({
|
|
|
1089
1172
|
setVariants: (
|
|
1090
1173
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
1091
1174
|
) => {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
)
|
|
1097
|
-
|
|
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
|
+
}
|
|
1098
1194
|
},
|
|
1099
1195
|
setDefaultVariants: (
|
|
1100
1196
|
newDefaults: VariantValues<Record<string, unknown>>,
|
|
@@ -1102,33 +1198,42 @@ export function create({
|
|
|
1102
1198
|
for (const key in newDefaults) {
|
|
1103
1199
|
if (!hasOwn.call(newDefaults, key)) continue;
|
|
1104
1200
|
if (variantProps[key] !== undefined) continue;
|
|
1105
|
-
if (disabledVariantKeys.has(key)) continue;
|
|
1106
1201
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
valueKey
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
+
}
|
|
1113
1211
|
}
|
|
1114
1212
|
updatedVariants[key] = value;
|
|
1115
1213
|
}
|
|
1116
1214
|
},
|
|
1117
|
-
addClass:
|
|
1118
|
-
addStyle:
|
|
1215
|
+
addClass: noop,
|
|
1216
|
+
addStyle: noop,
|
|
1119
1217
|
};
|
|
1120
1218
|
computed(ctx);
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1219
|
+
if (hasAnyDisabled) {
|
|
1220
|
+
const filteredUpdated: Record<string, unknown> = {};
|
|
1221
|
+
filterDisabledInto(updatedVariants, filteredUpdated);
|
|
1222
|
+
resolvedVariants = filteredUpdated;
|
|
1223
|
+
} else {
|
|
1224
|
+
resolvedVariants = updatedVariants;
|
|
1225
|
+
}
|
|
1124
1226
|
}
|
|
1125
1227
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1126
1228
|
};
|
|
1127
1229
|
|
|
1128
|
-
// Compute base class (without variants)
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
|
|
1230
|
+
// Compute base class (without variants) — includes extended base classes.
|
|
1231
|
+
// Plain `clsx` (no `transformClass`): `meta.baseClass` flows back into
|
|
1232
|
+
// parent extends as `clsx` input and then through the single
|
|
1233
|
+
// `transformClass(clsx(allClasses))` at render time, so applying it here
|
|
1234
|
+
// would compound (double for own-render, triple+ for extend chains) and
|
|
1235
|
+
// misbehave for non-idempotent transforms.
|
|
1236
|
+
const computedBaseClass = clsx(
|
|
1132
1237
|
...(extBaseClassesArr as ClsxClassValue[]),
|
|
1133
1238
|
config.class as ClsxClassValue,
|
|
1134
1239
|
);
|
|
@@ -1141,6 +1246,8 @@ export function create({
|
|
|
1141
1246
|
baseClass: computedBaseClass,
|
|
1142
1247
|
staticDefaults,
|
|
1143
1248
|
resolveDefaults: resolveDefaultsFn,
|
|
1249
|
+
compute,
|
|
1250
|
+
transformClass,
|
|
1144
1251
|
};
|
|
1145
1252
|
|
|
1146
1253
|
const initComponent = <
|
|
@@ -1213,4 +1320,6 @@ export function create({
|
|
|
1213
1320
|
return { cv, cx };
|
|
1214
1321
|
}
|
|
1215
1322
|
|
|
1323
|
+
function noop() {}
|
|
1324
|
+
|
|
1216
1325
|
export const { cv, cx } = create();
|