clava 0.4.1 → 0.5.0

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