UNPKG

@use-pico/cls

Version:

Type-safe, composable styling system for React, Vue, Svelte, and vanilla JS

1,191 lines (1,169 loc) 41.2 kB
import { twMerge } from 'tailwind-merge'; import { jsx } from 'react/jsx-runtime'; import { createContext, useContext, useMemo } from 'react'; /** * Runtime deduplication utility that maintains type compatibility * with the type-level deduplication */ function dedupe(arr) { const seen = new Set(); const result = []; for (const item of arr) { if (!seen.has(item)) { seen.add(item); result.push(item); } } return result; } /** * Concatenates and deduplicates two arrays */ function dedupeConcat(a, b) { const combined = [ ...a, ...b, ]; const seen = new Set(); const result = []; for (const item of combined) { if (!seen.has(item)) { seen.add(item); result.push(item); } } return result; } /** * Merges two variant objects, combining arrays for duplicate keys with deduplication. * This follows the same merging logic used throughout the CLS system * where variant values are accumulated rather than overridden, but duplicates are removed. * * @template TVariantsA - The first variant object type * @template TVariantsB - The second variant object type * @param variantsA - The base variant object * @param variantsB - The variant object to merge in * @returns A merged variant object with combined and deduplicated arrays for duplicate keys * * @example * ```typescript * const merged = mergeVariants( * { size: ["sm", "md"] }, * { size: ["lg", "md"], tone: ["light", "dark"] } * ); * // Result: { size: ["sm", "md", "lg"], tone: ["light", "dark"] } * ``` */ function mergeVariants(variantsA, variantsB) { const result = { ...variantsA, }; for (const [key, values] of Object.entries(variantsB)) { if (key in result && result[key]) { result[key] = [ ...new Set([ ...result[key], ...values, ]), ]; continue; } // Add new keys (already deduplicated if they come from another merge) result[key] = [ ...new Set(values), ]; } return result; } /** * Filters out undefined values from an object */ function filter(input) { if (!input) { return {}; } const result = {}; for (const [key, value] of Object.entries(input)) { if (value !== undefined) { result[key] = value; } } return result; } const cleanup = (tweak) => { return { ...tweak, token: filter(tweak.token), slot: filter(tweak.slot), variant: filter(tweak.variant), }; }; const tvc = (...classLists) => { const merged = twMerge(...classLists); if (!merged) { return ""; } const tokens = merged.split(/\s+/).filter(Boolean); const seen = new Set(); const outRev = []; for (let i = tokens.length - 1; i >= 0; i--) { const token = tokens[i]; if (token === undefined) { continue; } if (!seen.has(token)) { seen.add(token); outRev.push(token); } } return outRev.reverse().join(" "); }; /** * merge(tweak1, tweak2, ...) * * Merges multiple tweak objects with specific behavior: * - Variants are replaced by default (regardless of "override" flag) * - Slots are merged by default until override is explicitly set * - Tokens are merged until some tweak has override: true, then that tweak resets and starts over * - Override flag only affects slots/tokens explicitly set, not the whole tweak */ function tweaks(...tweaks) { if (!tweaks || tweaks.length === 0) { return {}; } const list = tweaks .flat(10) .filter((tweak) => tweak !== undefined); if (list.length === 0) { return {}; } const [root, ...rest] = list; return rest.reduce((acc, current) => { const override = current.override ?? false; const clear = current.clear ?? false; if (clear) { return { token: filter(current.token), slot: filter(current.slot), variant: filter(current.variant), }; } return { token: merge(acc.token ?? {}, current.token ?? {}, override), slot: merge(acc.slot ?? {}, current.slot ?? {}, override), variant: { ...filter(acc.variant), ...filter(current.variant), }, }; }, root); } const merge = (acc, current, override) => { const result = filter({ ...acc, }); Object.entries(filter(current)).forEach(([key, value]) => { if (!value) { return; } // Check both top-level override and What-level override const hasOverride = override || value.override === true; if (hasOverride) { result[key] = value; return; } if (!result[key]) { result[key] = value; return; } if ("class" in result[key] && "class" in value) { result[key].class = [ result[key].class, value.class, ]; } if ("token" in result[key] && "token" in value) { result[key].token = dedupe([ ...result[key].token, ...value.token, ]); } }); return result; }; // Standalone function to compute variants function withVariants(tweak, { contract, definition, }) { // Build inheritance chain (base -> child order) const layers = []; let current = contract; let currentDef = definition; while (current && currentDef) { layers.unshift({ contract: current, definition: currentDef, }); current = current["~use"]; currentDef = current?.["~definition"]; } // Merge defaults from ALL layers in inheritance order const defaultVariant = {}; // Process layers in inheritance order (base first, child last) for (const { definition } of layers) { // Merge defaults (child overrides base) Object.assign(defaultVariant, definition.defaults); } return { ...defaultVariant, ...tweak?.variant, }; } // ----------------------------------------------------------------------------- // Compile-time utilities (pure) // ----------------------------------------------------------------------------- const alwaysTrue = () => { return true; }; function compilePredicate(match) { if (!match) { return alwaysTrue; } const keys = Object.keys(match); return function pred(variant) { // Using loose index access intentionally to avoid narrowing gymnastics // Keys come directly from provided match object const variantAny = variant; return !keys.some((key) => { return variantAny[key] !== match[key]; }); }; } function buildLayers(contract, definition) { const layers = []; let currentContract = contract; let currentDefinition = definition; while (currentContract && currentDefinition) { layers.unshift({ contract: currentContract, definition: currentDefinition, }); currentContract = currentContract["~use"]; currentDefinition = currentContract ? currentContract["~definition"] : undefined; } return layers; } function collectSlotKeys(layers) { return Array.from(new Set(layers.flatMap(({ contract }) => contract.slot))); } function indexRulesBySlot(layers) { const rules = layers.flatMap(({ definition }) => { return definition.rules; }); const pairs = rules.flatMap((rule) => { const predicate = compilePredicate(rule.match); const isOverride = rule.override === true; const slotMap = rule.slot ?? {}; return Object.entries(slotMap) .filter((entry) => { return entry[1] !== undefined; }) .map(([slotKey, whatValue]) => { const compiledRule = { predicate, what: whatValue, override: isOverride, }; return { slotKey, compiledRule, }; }); }); return pairs.reduce((accumulator, pair) => { const list = accumulator[pair.slotKey] ?? []; list.push(pair.compiledRule); accumulator[pair.slotKey] = list; return accumulator; }, Object.create(null)); } function indexRulesByToken(layers) { const rules = layers.flatMap(({ definition }) => { return definition.rules; }); const pairs = rules.flatMap((rule) => { const predicate = compilePredicate(rule.match); const isOverride = rule.override === true; const tokenMap = rule.token ?? {}; return Object.entries(tokenMap) .filter((entry) => { return entry[1] !== undefined; }) .map(([tokenKey, whatValue]) => { const compiledRule = { predicate, what: whatValue, override: isOverride, }; return { tokenKey, compiledRule, }; }); }); return pairs.reduce((accumulator, pair) => { const list = accumulator[pair.tokenKey] ?? []; list.push(pair.compiledRule); accumulator[pair.tokenKey] = list; return accumulator; }, Object.create(null)); } function buildTokenTable(layers) { return layers.reduce((accumulator, item) => { const layerTokensEntries = Object.entries(item.definition.token ?? {}).filter(([, value]) => { return value !== undefined; }); const layerTokens = Object.fromEntries(layerTokensEntries); return Object.assign(Object.create(accumulator), layerTokens); }, Object.create(null)); } function compileContract(contract, definition) { const layers = buildLayers(contract, definition); const slotKeys = collectSlotKeys(layers); const rulesBySlot = indexRulesBySlot(layers); const rulesByToken = indexRulesByToken(layers); const tokensProto = buildTokenTable(layers); return { layers, slotKeys, rulesBySlot, rulesByToken, tokensProto, }; } // ----------------------------------------------------------------------------- // Resolver (factory) with cycle detection and caching // ----------------------------------------------------------------------------- function createResolver(baseTokenTable) { const baseResolvedTokenCache = Object.create(null); /** * Push class names into the accumulator, supporting string | string[] | nested arrays. * Order is preserved; falsy entries are ignored. */ function pushClassNames(accumulator, classInput) { if (Array.isArray(classInput)) { classInput.forEach((value) => { pushClassNames(accumulator, value); }); return; } if (typeof classInput === "string" && classInput) { accumulator.push(classInput); } } function resolve(whatValue, tokenTable = baseTokenTable, visiting = new Set(), localCache) { const out = []; const cacheRef = tokenTable === baseTokenTable ? baseResolvedTokenCache : localCache; if ("token" in whatValue && whatValue.token) { whatValue.token.forEach((tokenKey) => { if (visiting.has(tokenKey)) { throw new Error(`Circular dependency detected in token references: ${Array.from(visiting).join(" -> ")} -> ${tokenKey}`); } if (cacheRef?.[tokenKey]) { out.push(...cacheRef[tokenKey]); return; } const tokenDefinition = tokenTable[tokenKey]; if (!tokenDefinition) { return; } visiting.add(tokenKey); const resolved = resolve(tokenDefinition, tokenTable, visiting, localCache); visiting.delete(tokenKey); if (cacheRef) { cacheRef[tokenKey] = resolved.classes; } out.push(...resolved.classes); }); } if ("class" in whatValue && whatValue.class !== undefined) { pushClassNames(out, whatValue.class); } return { classes: out, override: whatValue.override === true, }; } return { resolve, baseResolvedTokenCache, }; } // ----------------------------------------------------------------------------- // Public API – cls // ----------------------------------------------------------------------------- function cls(contract, definition) { contract["~definition"] = definition; // Precompile layers, slots, rules and base token table const { slotKeys, rulesBySlot, rulesByToken, tokensProto } = compileContract(contract, definition); return { create(...tweak) { const $tweak = cleanup(tweaks(...tweak)); const variant = withVariants($tweak, { contract, definition, }); // Global token table with overrides via prototype chain const tokenTable = $tweak?.token ? Object.assign(Object.create(tokensProto), $tweak.token) : tokensProto; // Resolver bound to the token table after global overrides const { resolve } = createResolver(tokenTable); // Cache of built slot functions and result strings per local config const slotFunctionCache = Object.create(null); const resultCache = new Map(); // --- tweak key helpers (identity + stable stringify) --- const tweakIdentityIds = new WeakMap(); let tweakIdentitySeq = 0; /** * Recursively stringify an arbitrary POJO with sorted keys for determinism. * Mirrors JSON semantics for primitives and arrays; skips undefined values. * * TODO Revisit this - we know what the input is */ function stableStringifySorted(value) { if (value === null || typeof value !== "object") { return JSON.stringify(value); } if (Array.isArray(value)) { const items = value.map((item) => stableStringifySorted(item)); return `[${items.join(",")}]`; } const record = value; const keys = Object.keys(record).sort(); const parts = keys .filter((key) => record[key] !== undefined) .map((key) => { return `${JSON.stringify(key)}:${stableStringifySorted(record[key])}`; }); return `{${parts.join(",")}}`; } /** * Stable, slot-scoped cache key for local tweak. * Strategy: * 1) Identity-based key via WeakMap for reused object instances (fast path). * 2) Deterministic structural part that includes only fields that affect this slot. */ function computeKeyFromLocal(slotName, local) { if (!local) { return `${slotName}|__no_config__`; } // Identity key const localObject = local; let identityId = tweakIdentityIds.get(localObject); if (identityId === undefined) { tweakIdentitySeq = tweakIdentitySeq + 1; identityId = tweakIdentitySeq; tweakIdentityIds.set(localObject, identityId); } // Slot-scoped normalized view const normalized = {}; if (local.variant) { normalized.variant = local.variant; } const localSlotTable = local.slot; if (localSlotTable && Object.hasOwn(localSlotTable, slotName)) { normalized.slot = { [slotName]: localSlotTable[slotName], }; } if (local.token) { normalized.token = local.token; } try { const structural = stableStringifySorted(normalized); return `${slotName}|id:${identityId}|${structural}`; } catch { return null; } } const handler = { get(_, slotName) { if (slotName in slotFunctionCache) { return slotFunctionCache[slotName]; } const slotKeyStr = String(slotName); const slotFunction = (...local) => { const $local = cleanup(tweaks(...local)); // Merge variants (shallow, only defined values) const localEffective = { ...variant, }; if ($local?.variant) { Object.entries($local.variant) .filter(([, value]) => value !== undefined) .forEach(([key, value]) => { localEffective[key] = value; }); } const key = computeKeyFromLocal(slotKeyStr, $local); if (key !== null) { const cached = resultCache.get(key); if (cached !== undefined) { return cached; } } // --- Apply token rules to produce a per-call token overlay --- const tokenRulesOverlayEntries = Object.entries(rulesByToken) .map(([tokenKey, compiledRules]) => { const matches = compiledRules.map((rule) => { return rule.predicate(localEffective); }); const anyMatch = matches.some(Boolean); if (!anyMatch) { return null; } const lastOverrideIdx = compiledRules.reduce((acc, rule, index) => { if (matches[index] && rule.override) { return index; } return acc; }, -1); const startIndex = lastOverrideIdx >= 0 ? lastOverrideIdx : 0; const accumulated = compiledRules .slice(startIndex) .reduce((acc, rule, idx) => { if (!matches[startIndex + idx]) { return acc; } const resolved = resolve(rule.what); if (resolved.override) { acc.classes = resolved.classes.slice(); acc.tokens = []; } else { acc.classes = acc.classes.concat(resolved.classes); } if (rule.what.token) { acc.tokens = acc.tokens.concat(rule.what .token.filter(Boolean)); } return acc; }, { classes: [], tokens: [], }); const composed = {}; if (accumulated.classes.length > 0) { composed.class = accumulated.classes; } if (accumulated.tokens.length > 0) { composed.token = accumulated.tokens; } return [ tokenKey, composed, ]; }) .filter((x) => Boolean(x)); // Compose token tables with correct precedence (base < rules < create < local) const baseWithRuleOverlay = tokenRulesOverlayEntries.length > 0 ? Object.assign(Object.create(tokensProto), Object.fromEntries(tokenRulesOverlayEntries)) : Object.create(tokensProto); const tokenTableWithRuleOverlay = $tweak?.token ? Object.assign(Object.create(baseWithRuleOverlay), $tweak.token) : baseWithRuleOverlay; // Local token overlay and local cache for overlay resolution let activeTokens = tokenTableWithRuleOverlay; let localResolvedCache; if ($local?.token && Object.keys($local.token).length > 0) { activeTokens = Object.assign(Object.create(tokenTableWithRuleOverlay), $local.token); localResolvedCache = Object.create(null); } // Read per-slot customizations const localSlotWhat = $local?.slot ? $local.slot[slotName] : undefined; const configSlotWhat = $tweak?.slot ? $tweak.slot[slotName] : undefined; const slotRules = rulesBySlot[slotKeyStr] ?? []; // Fast path: nothing contributes const nothingContributes = [ slotRules.length === 0, !localSlotWhat, !configSlotWhat, ].every(Boolean); if (nothingContributes) { if (key !== null) { resultCache.set(key, ""); } return ""; } // Evaluate predicates and find last matching override index const matches = slotRules.map((rule) => { return rule.predicate(localEffective); }); const anyMatch = matches.some(Boolean); const lastOverrideIdx = slotRules.reduce((accumulator, rule, index) => { if (matches[index] && rule.override) { return index; } return accumulator; }, -1); let acc = []; if (anyMatch) { const startIndex = lastOverrideIdx >= 0 ? lastOverrideIdx : 0; const visiting = new Set(); slotRules.slice(startIndex).forEach((rule, idx) => { if (!matches[startIndex + idx]) { return; } const resolvedWhat = resolve(rule.what, activeTokens, visiting, localResolvedCache); if (resolvedWhat.override) { acc = resolvedWhat.classes; } else { acc = acc.concat(resolvedWhat.classes); } }); } // Append slot-level whats (config first, user last) if (configSlotWhat) { const resolvedWhat = resolve(configSlotWhat, activeTokens, new Set(), localResolvedCache); if (resolvedWhat.override) { acc = resolvedWhat.classes; } else { acc = acc.concat(resolvedWhat.classes); } } if (localSlotWhat) { const resolvedWhat = resolve(localSlotWhat, activeTokens, new Set(), localResolvedCache); if (resolvedWhat.override) { acc = resolvedWhat.classes; } else { acc = acc.concat(resolvedWhat.classes); } } const out = acc.length === 0 ? "" : tvc(acc); if (key !== null) { resultCache.set(key, out); } return out; }; slotFunctionCache[slotName] = slotFunction; return slotFunctionCache[slotName]; }, ownKeys() { return slotKeys; }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true, }; }, }; return { slots: new Proxy({}, handler), variant, }; }, extend(childContract, childDefinitionFn) { childContract["~use"] = contract; return cls(childContract, childDefinitionFn); }, use(sub) { return sub; }, tweak(...tweak) { return tweaks(...tweak); }, contract, definition, }; } /** * Creates a definition builder with the given state */ function builder$1(state) { return { token(token) { return builder$1({ ...state, token, }); }, tokens: { rule(match, token, override = false) { return builder$1({ ...state, rules: [ ...state.rules, { match, token, override, }, ], }); }, switch(key, whenTrue, whenFalse) { return builder$1({ ...state, rules: [ ...state.rules, { match: { [key]: true, }, token: whenTrue, override: false, }, { match: { [key]: false, }, token: whenFalse, override: false, }, ], }); }, match(key, value, token, override = false) { return builder$1({ ...state, rules: [ ...state.rules, { match: { [key]: value, }, token, override, }, ], }); }, }, root(slot, override = false) { return builder$1({ ...state, rules: [ ...state.rules, { match: undefined, slot: { _target: "slot", ...slot, }, override, }, ], }); }, rule(match, slot, override = false) { return builder$1({ ...state, rules: [ ...state.rules, { match, slot, override, }, ], }); }, switch(key, whenTrue, whenFalse) { return builder$1({ ...state, rules: [ ...state.rules, { match: { [key]: true, }, slot: whenTrue, override: false, }, { match: { [key]: false, }, slot: whenFalse, override: false, }, ], }); }, match(key, value, slot, override = false) { return builder$1({ ...state, rules: [ ...state.rules, { match: { [key]: value, }, slot, override, }, ], }); }, defaults(defaults) { return builder$1({ ...state, defaults, }); }, cls() { return cls(state.contract, { token: state.token || {}, rules: state.rules, defaults: state.defaults || {}, }); }, }; } const definition = (contract, use) => { return builder$1({ contract, rules: [], use, }); }; /** * Creates a contract builder with the given state */ function builder(state) { return { tokens(tokens) { return builder({ ...state, tokens: dedupeConcat(state.tokens, tokens), }); }, token(token) { return builder({ ...state, tokens: dedupeConcat(state.tokens, [ token, ]), }); }, slots(slots) { return builder({ ...state, slot: dedupeConcat(state.slot, slots), }); }, slot(slot) { return builder({ ...state, slot: dedupeConcat(state.slot, [ slot, ]), }); }, variants(variants) { return builder({ ...state, variant: mergeVariants(state.variant, variants), }); }, variant(name, values) { return builder({ ...state, variant: mergeVariants(state.variant, { [name]: values, }), }); }, bool(name) { return builder({ ...state, variant: mergeVariants(state.variant, { [name]: [ "bool", ], }), }); }, build() { const { use, ...contract } = state; return { ...contract, /** * Important piece - this will enable inheritance. */ "~use": use, /** * Definition - not yet */ "~definition": undefined, }; }, def() { return definition(this.build(), state.use); }, }; } function contract(use) { return builder({ tokens: [], slot: [], variant: {}, use, }); } /** * Context for providing token tweaks to child components. * This affects only tokens, keeping naming clear. */ const TokenContext = createContext({}); /** * Provider component that extracts tokens from a CLS instance and provides them via context. * * This component takes a CLS instance and extracts its token definitions to provide them * to child components via TokenContext. This makes the API more intuitive as you can pass * a complete CLS instance rather than manually extracting tokens. * * @template TContract - The contract type of the CLS instance * @param cls - CLS instance to extract tokens from * @param children - Child components that will receive the token context * * @example * ```tsx * // Pass a theme CLS instance * <TokenProvider cls={ThemeCls}> * <Button>Click me</Button> * </TokenProvider> * ``` * * @example * ```tsx * // Pass a component CLS instance for token overrides * <TokenProvider cls={CustomButtonCls}> * <Button>Custom styled button</Button> * </TokenProvider> * ``` */ function TokenProvider({ cls, children, }) { return (jsx(TokenContext, { value: cls?.definition?.token, children: children })); } /** * Hook to access the TokenContext. * * Returns the current token tweaks provided via context, * or undefined if no provider is present. Only tokens are affected by this context. */ function useTokenContext() { return useContext(TokenContext); } /** * React context for "user-land" tweaks. * * Purpose: * - Components using `useCls` automatically read from the global `TokenContext`. * - `TweakContext` is the second layer consulted by `useCls` for overrides. * - Pure user-land tweaks take precedence over all other sources. * * Use this to scope tweak overrides to a subtree. Values are merged by `useCls` * after `TokenContext` and before internal defaults, with direct user tweaks * winning on conflicts. * * @returns React context carrying tweak values for any contract. * * @example * ```tsx * <TweakContext value={{ size: ["lg"], intent: ["primary"] }}> * <Child /> * </TweakContext> * * // Inside a descendant component * const tweak = useTweakContext(); * ``` */ const VariantContext = createContext({}); const useVariantContext = () => { return useContext(VariantContext); }; /** * React hook to create a CLS kit from a cls instance, merging user tweaks with context. * * **Tweak Precedence (highest to lowest):** * 1. **User tweak** - Direct user customization passed as parameter * 2. **Context tweak** - Automatically merged from TokenContext and VariantContext * * **Context Integration:** * - Automatically subscribes to `TokenContext` for token overrides * - Automatically subscribes to `VariantContext` for variant overrides * - Context values have lower precedence than user-provided tweaks * * **Tweak Merging:** * - Uses the `tweaks()` function with parameter-based syntax * - User tweak takes precedence over context values * - Undefined values are automatically filtered out * * @template TContract - CLS contract type defining tokens, slots, and variants * @param cls - CLS instance to create kit from * @param tweaks - Optional user-provided tweak (variant/slot/token/override) * @returns A `Cls.Kit<TContract>` with slot functions (e.g., `slots.root()`) and resolved variants * * @example * ```tsx * // Basic usage with user tweak * const { slots, variant } = useCls(ButtonCls, { * variant: { size: "lg", tone: "primary" }, * slot: { root: { class: ["font-bold"] } } * }); * ``` * * @example * ```tsx * // Without user tweak - uses context only * const { slots, variant } = useCls(ButtonCls); * ``` * * @example * ```tsx * // User tweak overrides context values * const { slots, variant } = useCls(ButtonCls, { * variant: { size: "sm" } // Overrides context variant.size * }); * ``` */ function useCls(cls, ...tweaks) { const token = useTokenContext(); const variant = useVariantContext(); return cls.create({ token, variant, }, ...tweaks); } /** * Memoized version of `useCls` with explicit dependency control for performance optimization. * * **Tweak Precedence (highest to lowest):** * 1. **User tweak** - Direct user customization passed as parameter * 2. **Context tweak** - Automatically merged from TokenContext and VariantContext * * **Context Integration:** * - Automatically subscribes to `TokenContext` for token overrides * - Automatically subscribes to `VariantContext` for variant overrides * - Context values have lower precedence than user-provided tweaks * * **Memoization Control:** * - Uses `useMemo` with the provided dependency list * - Recreates the CLS kit only when dependencies change * - Essential for performance when tweaks depend on props or state * * **Tweak Merging:** * - Uses the `tweaks()` function with parameter-based syntax * - User tweak takes precedence over context values * - Undefined values are automatically filtered out * * @template TContract - CLS contract type defining tokens, slots, and variants * @param cls - CLS instance to create kit from * @param tweaks - Optional user-provided tweak (variant/slot/token/override) * @param deps - Dependency list controlling memoization (defaults to empty array) * @returns A memoized `Cls.Kit<TContract>` with slot functions (e.g., `slots.root()`) and resolved variants * * @example * ```tsx * // Basic usage with memoization based on props * const { slots, variant } = useClsMemo( * ButtonCls, * { variant: { size, tone } }, * [size, tone] // Re-memoize when size or tone changes * ); * ``` * * @example * ```tsx * // Complex tweak with multiple dependencies * const { slots, variant } = useClsMemo( * ButtonCls, * { * variant: { size, tone, disabled }, * slot: { root: { class: disabled ? ["opacity-50"] : [] } } * }, * [size, tone, disabled] // Re-memoize when any dependency changes * ); * ``` * * @example * ```tsx * // No user tweak - memoizes context-only result * const { slots, variant } = useClsMemo( * ButtonCls, * undefined, * [] // Never re-memoize (context changes handled internally) * ); * ``` */ function useClsMemo(cls, tweaks, deps = []) { const token = useTokenContext(); const variant = useVariantContext(); return useMemo(() => cls.create( /** * Context tweak has lowest priority */ { token, variant, }, tweaks), // biome-ignore lint/correctness/useExhaustiveDependencies: User driven deps); } const VariantProvider = ({ /** * Used only to infer types */ cls: _, variant, inherit = false, children, }) => { /** * A little lie here, but in general it should be somehow OK. */ const parent = useVariantContext(); return (jsx(VariantContext, { value: tweaks([ { variant, }, inherit ? { variant: parent, } : undefined, ]).variant ?? {}, children: children })); }; /** * React wrapper - useful for preparing type-safe variants for React components. */ const wrap = (cls) => { return { VariantProvider(props) { return (jsx(VariantProvider, { cls: cls, ...props })); }, }; }; /** * Just a true boolean. * * I don't like boolean hells, so this is a little compromise. * * You _can_ use this for rules, when you want override to keep it * explicit, purely optional for you. */ const OVERRIDE = true; export { OVERRIDE, TokenProvider, VariantProvider, cls, contract, definition, tvc, useCls, useClsMemo, useTokenContext, useVariantContext, wrap }; //# sourceMappingURL=index.js.map