clava 0.2.2 → 0.2.4
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.d.ts +1 -1
- package/dist/index.js +244 -218
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +468 -373
- package/src/types.ts +7 -5
- 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/tests/{variants-test.ts → variants.test.ts} +29 -1
- 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/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[];
|
|
@@ -480,75 +471,6 @@ function buildPrebuiltVariant(variantDef: unknown): PrebuiltVariant {
|
|
|
480
471
|
};
|
|
481
472
|
}
|
|
482
473
|
|
|
483
|
-
/**
|
|
484
|
-
* Creates the resolveDefaults function for a component. This function returns
|
|
485
|
-
* only the variants set via setDefaultVariants in the computed function. Used
|
|
486
|
-
* by child components to get parent's computed defaults.
|
|
487
|
-
*/
|
|
488
|
-
function createResolveDefaults(
|
|
489
|
-
config: CVConfig<Variants, ComputedVariants, AnyComponent[]>,
|
|
490
|
-
staticDefaults: Record<string, unknown>,
|
|
491
|
-
): ComponentMeta["resolveDefaults"] {
|
|
492
|
-
const computed = config.computed;
|
|
493
|
-
const extend = config.extend;
|
|
494
|
-
return (childDefaults, userProps = {}) => {
|
|
495
|
-
// Merge: parent static < child static < user props
|
|
496
|
-
// This is what parent's computed will see in `variants`
|
|
497
|
-
const resolvedVariants: Record<string, unknown> = {};
|
|
498
|
-
Object.assign(resolvedVariants, staticDefaults);
|
|
499
|
-
for (const key in childDefaults) {
|
|
500
|
-
if (!hasOwn.call(childDefaults, key)) continue;
|
|
501
|
-
const v = childDefaults[key];
|
|
502
|
-
if (v === undefined) continue;
|
|
503
|
-
resolvedVariants[key] = v;
|
|
504
|
-
}
|
|
505
|
-
for (const key in userProps) {
|
|
506
|
-
if (!hasOwn.call(userProps, key)) continue;
|
|
507
|
-
const v = userProps[key];
|
|
508
|
-
if (v === undefined) continue;
|
|
509
|
-
resolvedVariants[key] = v;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Track which keys are set via setDefaultVariants
|
|
513
|
-
const computedDefaults: Record<string, unknown> = {};
|
|
514
|
-
|
|
515
|
-
// Propagate to extended components so their computed functions can run
|
|
516
|
-
if (extend) {
|
|
517
|
-
for (const ext of extend) {
|
|
518
|
-
const meta = getComponentMeta(ext);
|
|
519
|
-
if (!meta) continue;
|
|
520
|
-
const extDefaults = meta.resolveDefaults(childDefaults, userProps);
|
|
521
|
-
for (const k in extDefaults) {
|
|
522
|
-
if (hasOwn.call(extDefaults, k)) {
|
|
523
|
-
computedDefaults[k] = extDefaults[k];
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (computed) {
|
|
530
|
-
computed({
|
|
531
|
-
variants: resolvedVariants as VariantValues<Record<string, unknown>>,
|
|
532
|
-
setVariants: () => {},
|
|
533
|
-
setDefaultVariants: (newDefaults) => {
|
|
534
|
-
for (const key in newDefaults) {
|
|
535
|
-
if (!hasOwn.call(newDefaults, key)) continue;
|
|
536
|
-
const value = (newDefaults as Record<string, unknown>)[key];
|
|
537
|
-
if (userProps[key] !== undefined) continue;
|
|
538
|
-
if (isVariantDisabled(config, key)) continue;
|
|
539
|
-
if (isVariantValueDisabled(config, key, value)) continue;
|
|
540
|
-
computedDefaults[key] = value;
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
addClass: () => {},
|
|
544
|
-
addStyle: () => {},
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return computedDefaults;
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
|
|
552
474
|
/**
|
|
553
475
|
* Creates the cv and cx functions.
|
|
554
476
|
*/
|
|
@@ -568,6 +490,7 @@ export function create({
|
|
|
568
490
|
|
|
569
491
|
// ----- Pre-computed at creation time -----
|
|
570
492
|
const variantKeys = collectVariantKeys(config);
|
|
493
|
+
const variantKeysLength = variantKeys.length;
|
|
571
494
|
const disabledVariantKeys = collectDisabledVariantKeys(config);
|
|
572
495
|
const disabledVariantValues = collectDisabledVariantValues(config);
|
|
573
496
|
const hasDisabledVariantKeys = disabledVariantKeys.size > 0;
|
|
@@ -583,8 +506,7 @@ export function create({
|
|
|
583
506
|
const computedVariantsCfg = config.computedVariants;
|
|
584
507
|
const computed = config.computed;
|
|
585
508
|
const baseStyle = config.style;
|
|
586
|
-
const
|
|
587
|
-
config.class === undefined ? null : (config.class as ClassValue);
|
|
509
|
+
const hasBaseStyle = !!baseStyle;
|
|
588
510
|
|
|
589
511
|
// Pre-build variant entries for fast iteration. For each variant key in
|
|
590
512
|
// `variants`, we have a name and a PrebuiltVariant with normalized values.
|
|
@@ -664,22 +586,66 @@ export function create({
|
|
|
664
586
|
}
|
|
665
587
|
|
|
666
588
|
// Pre-build extended component info, so we don't have to call
|
|
667
|
-
// `getComponentMeta` per render.
|
|
668
|
-
|
|
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[] = [];
|
|
669
596
|
const extBaseClassesArr: string[] = [];
|
|
670
|
-
const
|
|
597
|
+
const extIsolated: boolean[] = [];
|
|
598
|
+
let hasIsolatedExt = false;
|
|
671
599
|
if (extend) {
|
|
672
600
|
for (const ext of extend) {
|
|
673
601
|
const meta = getComponentMeta(ext);
|
|
602
|
+
if (!meta) continue;
|
|
674
603
|
extMetas.push(meta);
|
|
675
|
-
|
|
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
|
+
}
|
|
676
615
|
}
|
|
677
616
|
}
|
|
678
|
-
const extCount =
|
|
679
|
-
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
//
|
|
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.
|
|
683
649
|
function filterDisabledInto(
|
|
684
650
|
input: Record<string, unknown>,
|
|
685
651
|
out: Record<string, unknown>,
|
|
@@ -704,12 +670,82 @@ export function create({
|
|
|
704
670
|
}
|
|
705
671
|
}
|
|
706
672
|
|
|
707
|
-
// Pre-create
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
|
|
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
|
+
}
|
|
742
|
+
|
|
743
|
+
return computedDefaults;
|
|
744
|
+
}
|
|
745
|
+
: null;
|
|
711
746
|
|
|
712
|
-
//
|
|
747
|
+
// Hot path: resolve variants by merging static defaults + extends'
|
|
748
|
+
// computed defaults + user-provided props.
|
|
713
749
|
function resolveVariantsHot(
|
|
714
750
|
propsVariants: Record<string, unknown>,
|
|
715
751
|
): Record<string, unknown> {
|
|
@@ -717,22 +753,20 @@ export function create({
|
|
|
717
753
|
const defaults: Record<string, unknown> = {};
|
|
718
754
|
Object.assign(defaults, staticDefaults);
|
|
719
755
|
|
|
720
|
-
// Apply computed defaults from extended components
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
defaults[k] = extComputed[k];
|
|
729
|
-
}
|
|
730
|
-
}
|
|
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];
|
|
731
764
|
}
|
|
732
765
|
}
|
|
733
766
|
|
|
734
|
-
//
|
|
735
|
-
//
|
|
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.
|
|
736
770
|
for (const k in propsVariants) {
|
|
737
771
|
if (!hasOwn.call(propsVariants, k)) continue;
|
|
738
772
|
const v = propsVariants[k];
|
|
@@ -748,54 +782,70 @@ export function create({
|
|
|
748
782
|
return result;
|
|
749
783
|
}
|
|
750
784
|
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (key in props) {
|
|
769
|
-
variantProps[key] = (props as Record<string, unknown>)[key];
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// Resolve variants with defaults
|
|
774
|
-
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
775
|
-
|
|
776
|
-
// Run computed function (may update variants and emit class/style)
|
|
777
|
-
let computedClassesArr: ClassValue[] | null = null;
|
|
778
|
-
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;
|
|
779
802
|
|
|
780
803
|
if (computed) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
+
};
|
|
785
826
|
const ctx = {
|
|
786
|
-
variants:
|
|
827
|
+
variants: ownVariants as VariantValues<Record<string, unknown>>,
|
|
787
828
|
setVariants: (
|
|
788
829
|
newVariants: VariantValues<Record<string, unknown>>,
|
|
789
830
|
) => {
|
|
790
831
|
if (!hasAnyDisabled) {
|
|
791
|
-
Object.assign(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
);
|
|
798
|
-
|
|
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;
|
|
799
849
|
}
|
|
800
850
|
},
|
|
801
851
|
setDefaultVariants: (
|
|
@@ -803,7 +853,7 @@ export function create({
|
|
|
803
853
|
) => {
|
|
804
854
|
for (const key in newDefaults) {
|
|
805
855
|
if (!hasOwn.call(newDefaults, key)) continue;
|
|
806
|
-
if (
|
|
856
|
+
if (userVariantProps[key] !== undefined) continue;
|
|
807
857
|
const value = (newDefaults as Record<string, unknown>)[key];
|
|
808
858
|
if (hasAnyDisabled) {
|
|
809
859
|
if (disabledVariantKeys.has(key)) continue;
|
|
@@ -815,174 +865,140 @@ export function create({
|
|
|
815
865
|
continue;
|
|
816
866
|
}
|
|
817
867
|
}
|
|
818
|
-
|
|
868
|
+
ensureUpdated()[key] = value;
|
|
819
869
|
}
|
|
820
870
|
},
|
|
821
871
|
addClass: (className: ClassValue) => {
|
|
822
|
-
|
|
872
|
+
localCClasses.push(className);
|
|
823
873
|
},
|
|
824
874
|
addStyle: (newStyle: StyleValue) => {
|
|
825
|
-
if (!
|
|
826
|
-
assign(
|
|
875
|
+
if (!localCStyle) localCStyle = {};
|
|
876
|
+
Object.assign(localCStyle, newStyle);
|
|
827
877
|
},
|
|
828
878
|
};
|
|
829
879
|
const result = computed(ctx);
|
|
830
880
|
if (result != null) {
|
|
831
881
|
const r = extractClassAndStylePrebuilt(result);
|
|
832
|
-
if (r.class != null)
|
|
882
|
+
if (r.class != null) localCClasses.push(r.class);
|
|
833
883
|
if (r.style) {
|
|
834
|
-
if (!
|
|
835
|
-
assign(
|
|
884
|
+
if (!localCStyle) localCStyle = {};
|
|
885
|
+
Object.assign(localCStyle, r.style);
|
|
836
886
|
}
|
|
837
887
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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;
|
|
897
|
+
}
|
|
844
898
|
}
|
|
845
|
-
computedClassesArr = cClasses;
|
|
846
|
-
computedStyleObj = cStyle;
|
|
847
899
|
}
|
|
848
900
|
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
for (const k of
|
|
859
|
-
}
|
|
860
|
-
// computedVariantKeys is currentVariantKeys + computedVariants names
|
|
861
|
-
let computedVariantKeysSet: Set<string> | null = null;
|
|
862
|
-
if (hasExtend) {
|
|
863
|
-
if (currentVariantKeys || computedVariantNames.length > 0) {
|
|
864
|
-
computedVariantKeysSet = new Set<string>();
|
|
865
|
-
if (currentVariantKeys) {
|
|
866
|
-
for (const k of currentVariantKeys) computedVariantKeysSet.add(k);
|
|
867
|
-
}
|
|
868
|
-
for (let i = 0; i < computedVariantNames.length; i++) {
|
|
869
|
-
computedVariantKeysSet.add(computedVariantNames[i]);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
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);
|
|
872
911
|
}
|
|
873
912
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const set = new Set<string>();
|
|
884
|
-
for (const v of skipStyleVariantValuesIn[k]) {
|
|
885
|
-
set.add(v);
|
|
886
|
-
}
|
|
887
|
-
computedVariantValues[k] = set;
|
|
888
|
-
}
|
|
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];
|
|
889
922
|
}
|
|
890
|
-
for (
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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];
|
|
896
931
|
}
|
|
897
|
-
for (const v of disabledVariantValues[k]) bucket.add(v);
|
|
898
932
|
}
|
|
899
933
|
}
|
|
900
934
|
|
|
901
|
-
//
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
//
|
|
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.
|
|
913
950
|
if (hasExtend) {
|
|
914
|
-
const hasComputedVariantKeysSet =
|
|
915
|
-
!!computedVariantKeysSet && computedVariantKeysSet.size > 0;
|
|
916
|
-
const hasComputedVariantValues =
|
|
917
|
-
!!computedVariantValues &&
|
|
918
|
-
Object.keys(computedVariantValues).length > 0;
|
|
919
|
-
const hasSkipForExt =
|
|
920
|
-
hasComputedVariantKeysSet || hasComputedVariantValues;
|
|
921
|
-
|
|
922
|
-
const extVariantClasses: ClassValue[] = [];
|
|
923
|
-
|
|
924
951
|
for (let i = 0; i < extCount; i++) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
+
);
|
|
934
971
|
}
|
|
935
972
|
}
|
|
936
|
-
if (hasComputedVariantKeysSet) {
|
|
937
|
-
propsForExt[SKIP_STYLE_KEYS] = computedVariantKeysSet;
|
|
938
|
-
}
|
|
939
|
-
if (hasComputedVariantValues) {
|
|
940
|
-
propsForExt[SKIP_STYLE_VARIANT_VALUES] = computedVariantValues;
|
|
941
|
-
}
|
|
942
973
|
} else {
|
|
943
|
-
|
|
974
|
+
extMetas[i].compute(
|
|
975
|
+
workingResolved,
|
|
976
|
+
workingResolved,
|
|
977
|
+
extSkipKeys,
|
|
978
|
+
extSkipVals,
|
|
979
|
+
classesOut,
|
|
980
|
+
styleOut,
|
|
981
|
+
);
|
|
944
982
|
}
|
|
945
|
-
|
|
946
|
-
const extResult = ext(
|
|
947
|
-
propsForExt as ComponentProps<Record<string, unknown>>,
|
|
948
|
-
);
|
|
949
|
-
// ext may be a modal component (.html / .htmlObj), whose style is a
|
|
950
|
-
// CSS string or hyphen-keyed object — normalize before merging.
|
|
951
|
-
if (extResult.style != null) {
|
|
952
|
-
assign(allStyle, normalizeStyle(extResult.style));
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
allClasses.push(extBaseClass);
|
|
956
|
-
const fullClass =
|
|
957
|
-
"className" in extResult ? extResult.className : extResult.class;
|
|
958
|
-
const variantPortion = extractVariantClasses(fullClass, extBaseClass);
|
|
959
|
-
if (variantPortion) extVariantClasses.push(variantPortion);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// 2. Current base class
|
|
963
|
-
allClasses.push(baseClass);
|
|
964
|
-
if (baseStyle) assign(allStyle, baseStyle);
|
|
965
|
-
|
|
966
|
-
// 4. Extended variant classes
|
|
967
|
-
for (let i = 0; i < extVariantClasses.length; i++) {
|
|
968
|
-
allClasses.push(extVariantClasses[i]);
|
|
969
983
|
}
|
|
970
|
-
} else {
|
|
971
|
-
// No extends: just current base
|
|
972
|
-
allClasses.push(baseClass);
|
|
973
|
-
if (baseStyle) assign(allStyle, baseStyle);
|
|
974
984
|
}
|
|
975
985
|
|
|
976
|
-
//
|
|
977
|
-
|
|
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;
|
|
978
995
|
for (let i = 0; i < variantEntryCount; i++) {
|
|
979
996
|
const variantName = variantEntryNames[i];
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const selectedValue = resolvedVariants[variantName];
|
|
997
|
+
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
998
|
+
const selectedValue = workingResolved[variantName];
|
|
983
999
|
if (selectedValue === undefined) continue;
|
|
984
1000
|
const selectedKey = getVariantValueKey(selectedValue);
|
|
985
|
-
|
|
1001
|
+
const variant = variantEntryDefs[i];
|
|
986
1002
|
if (
|
|
987
1003
|
variant.disabledValues &&
|
|
988
1004
|
selectedKey != null &&
|
|
@@ -990,13 +1006,10 @@ export function create({
|
|
|
990
1006
|
) {
|
|
991
1007
|
continue;
|
|
992
1008
|
}
|
|
993
|
-
// skipVariantValues comes from skipStyleVariantValuesIn (only relevant
|
|
994
|
-
// if this is being called as an extended component). For top-level it
|
|
995
|
-
// would be undefined.
|
|
996
1009
|
if (
|
|
997
|
-
|
|
1010
|
+
ownSkipValues &&
|
|
998
1011
|
selectedKey != null &&
|
|
999
|
-
|
|
1012
|
+
ownSkipValues[variantName]?.has(selectedKey)
|
|
1000
1013
|
) {
|
|
1001
1014
|
continue;
|
|
1002
1015
|
}
|
|
@@ -1005,83 +1018,149 @@ export function create({
|
|
|
1005
1018
|
if (selectedKey == null) continue;
|
|
1006
1019
|
const v = variant.values[selectedKey];
|
|
1007
1020
|
if (!v) continue;
|
|
1008
|
-
if (v.class != null)
|
|
1009
|
-
if (v.style) assign(
|
|
1010
|
-
} else if (variant.shorthand) {
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
if (v.class != null) allClasses.push(v.class);
|
|
1015
|
-
if (v.style) assign(allStyle, v.style);
|
|
1016
|
-
}
|
|
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);
|
|
1017
1027
|
}
|
|
1018
1028
|
}
|
|
1019
1029
|
|
|
1020
|
-
// computedVariants
|
|
1030
|
+
// Apply computedVariants.
|
|
1021
1031
|
for (let i = 0; i < computedVariantCount; i++) {
|
|
1022
1032
|
const variantName = computedVariantNames[i];
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const selectedValue = resolvedVariants[variantName];
|
|
1033
|
+
if (ownSkipKeys && ownSkipKeys.has(variantName)) continue;
|
|
1034
|
+
const selectedValue = workingResolved[variantName];
|
|
1026
1035
|
if (selectedValue === undefined) continue;
|
|
1027
1036
|
const selectedKey = getVariantValueKey(selectedValue);
|
|
1028
1037
|
if (
|
|
1029
|
-
|
|
1038
|
+
ownSkipValues &&
|
|
1030
1039
|
selectedKey != null &&
|
|
1031
|
-
|
|
1040
|
+
ownSkipValues[variantName]?.has(selectedKey)
|
|
1032
1041
|
) {
|
|
1033
1042
|
continue;
|
|
1034
1043
|
}
|
|
1044
|
+
const fn = computedVariantFns[i];
|
|
1035
1045
|
const computedResult = fn(selectedValue);
|
|
1036
1046
|
if (computedResult == null) continue;
|
|
1037
1047
|
const r = extractClassAndStylePrebuilt(computedResult);
|
|
1038
|
-
if (r.class != null)
|
|
1039
|
-
if (r.style) assign(
|
|
1048
|
+
if (r.class != null) classesOut.push(r.class as ClsxClassValue);
|
|
1049
|
+
if (r.style) Object.assign(styleOut, r.style);
|
|
1040
1050
|
}
|
|
1041
1051
|
|
|
1042
|
-
// computed
|
|
1043
|
-
if (
|
|
1044
|
-
for (let i = 0; i <
|
|
1045
|
-
|
|
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
|
+
}
|
|
1046
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;
|
|
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;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (hasAnyDisabled) {
|
|
1116
|
+
const filtered: Record<string, unknown> = {};
|
|
1117
|
+
filterDisabledInto(resolved, filtered);
|
|
1118
|
+
resolved = filtered;
|
|
1047
1119
|
}
|
|
1048
|
-
if (computedStyleObj) assign(allStyle, computedStyleObj);
|
|
1049
1120
|
|
|
1050
|
-
//
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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);
|
|
1055
1127
|
|
|
1056
|
-
//
|
|
1057
|
-
|
|
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
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Apply user-provided style.
|
|
1137
|
+
const psv = propsRecord.style;
|
|
1058
1138
|
if (psv != null) {
|
|
1059
|
-
// Fast path: if it's an object with no keys, skip
|
|
1060
1139
|
if (typeof psv === "string") {
|
|
1061
1140
|
if (psv.length > 0) {
|
|
1062
|
-
assign(
|
|
1141
|
+
Object.assign(style, htmlStyleToStyleValue(psv));
|
|
1063
1142
|
}
|
|
1064
1143
|
} else if (typeof psv === "object") {
|
|
1065
|
-
//
|
|
1144
|
+
// Don't allocate when empty.
|
|
1066
1145
|
let hasAnyKey = false;
|
|
1067
1146
|
for (const _ in psv) {
|
|
1068
1147
|
hasAnyKey = true;
|
|
1069
1148
|
break;
|
|
1070
1149
|
}
|
|
1071
1150
|
if (hasAnyKey) {
|
|
1072
|
-
assign(
|
|
1151
|
+
Object.assign(style, normalizeStyle(psv));
|
|
1073
1152
|
}
|
|
1074
1153
|
}
|
|
1075
1154
|
}
|
|
1076
1155
|
|
|
1077
1156
|
return {
|
|
1078
|
-
className:
|
|
1079
|
-
style
|
|
1157
|
+
className: transformClass(clsx(allClasses)),
|
|
1158
|
+
style,
|
|
1080
1159
|
};
|
|
1081
1160
|
};
|
|
1082
1161
|
|
|
1083
1162
|
const getVariants = (variants?: VariantValues<MergedVariants>) => {
|
|
1084
|
-
const variantProps =
|
|
1163
|
+
const variantProps = variants ?? EMPTY_DEFAULTS;
|
|
1085
1164
|
let resolvedVariants = resolveVariantsHot(variantProps);
|
|
1086
1165
|
// Run computed function to get variants set via setVariants and
|
|
1087
1166
|
// setDefaultVariants
|
|
@@ -1095,13 +1174,22 @@ export function create({
|
|
|
1095
1174
|
) => {
|
|
1096
1175
|
if (!hasAnyDisabled) {
|
|
1097
1176
|
Object.assign(updatedVariants, newVariants);
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
);
|
|
1104
|
-
|
|
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;
|
|
1105
1193
|
}
|
|
1106
1194
|
},
|
|
1107
1195
|
setDefaultVariants: (
|
|
@@ -1124,8 +1212,8 @@ export function create({
|
|
|
1124
1212
|
updatedVariants[key] = value;
|
|
1125
1213
|
}
|
|
1126
1214
|
},
|
|
1127
|
-
addClass:
|
|
1128
|
-
addStyle:
|
|
1215
|
+
addClass: noop,
|
|
1216
|
+
addStyle: noop,
|
|
1129
1217
|
};
|
|
1130
1218
|
computed(ctx);
|
|
1131
1219
|
if (hasAnyDisabled) {
|
|
@@ -1139,10 +1227,13 @@ export function create({
|
|
|
1139
1227
|
return resolvedVariants as VariantValues<MergedVariants>;
|
|
1140
1228
|
};
|
|
1141
1229
|
|
|
1142
|
-
// Compute base class (without variants)
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1145
|
-
|
|
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(
|
|
1146
1237
|
...(extBaseClassesArr as ClsxClassValue[]),
|
|
1147
1238
|
config.class as ClsxClassValue,
|
|
1148
1239
|
);
|
|
@@ -1155,6 +1246,8 @@ export function create({
|
|
|
1155
1246
|
baseClass: computedBaseClass,
|
|
1156
1247
|
staticDefaults,
|
|
1157
1248
|
resolveDefaults: resolveDefaultsFn,
|
|
1249
|
+
compute,
|
|
1250
|
+
transformClass,
|
|
1158
1251
|
};
|
|
1159
1252
|
|
|
1160
1253
|
const initComponent = <
|
|
@@ -1227,4 +1320,6 @@ export function create({
|
|
|
1227
1320
|
return { cv, cx };
|
|
1228
1321
|
}
|
|
1229
1322
|
|
|
1323
|
+
function noop() {}
|
|
1324
|
+
|
|
1230
1325
|
export const { cv, cx } = create();
|