@use-pico/cls
Version:
Type-safe, composable styling system for React, Vue, Svelte, and vanilla JS
134 lines (122 loc) • 3.23 kB
text/typescript
import type { Contract, CreateConfig, What, WhatConfigFn } from "./types";
import { what } from "./what";
/**
* Combines two What objects by merging their class and token arrays
*/
function combineWhat<T extends Contract<any, any, any>>(
internal: What<T> | undefined,
user: What<T> | undefined,
): What<T> | undefined {
if (!internal && !user) {
return undefined;
}
if (!internal) {
return user;
}
if (!user) {
return internal;
}
// Combine class arrays
const internalClasses = "class" in internal ? internal.class : [];
const userClasses = "class" in user ? user.class : [];
const combinedClasses = [
...(Array.isArray(internalClasses)
? internalClasses
: [
internalClasses,
]),
...(Array.isArray(userClasses)
? userClasses
: [
userClasses,
]),
];
// Combine token arrays
const internalTokens = "token" in internal ? internal.token : [];
const userTokens = "token" in user ? user.token : [];
const combinedTokens = [
...(Array.isArray(internalTokens)
? internalTokens
: [
internalTokens,
]),
...(Array.isArray(userTokens)
? userTokens
: [
userTokens,
]),
];
// Create result based on what we have
if (combinedClasses.length > 0 && combinedTokens.length > 0) {
return {
class: combinedClasses,
token: combinedTokens,
} as What<T>;
} else if (combinedClasses.length > 0) {
return {
class: combinedClasses,
} as What<T>;
} else if (combinedTokens.length > 0) {
return {
token: combinedTokens,
} as What<T>;
}
return undefined;
}
/**
* merge(user, internal)
*
* Merges two CreateConfig objects of the same contract type.
* - Field-level precedence: user wins over internal (variant, slot, override, token)
* - Shallow merge per field to match cls.create() semantics
* - Slots are combined by appending What objects, not overriding them
*/
export function merge<const TContract extends Contract<any, any, any>>(
userFn?: WhatConfigFn<TContract>,
internalFn?: WhatConfigFn<TContract>,
): () => Partial<CreateConfig<TContract>> {
const $user = userFn?.(what());
const $internal = internalFn?.(what());
return () => ({
...($internal ?? {}),
...($user ?? {}),
variant: {
...$internal?.variant,
...$user?.variant,
} as Partial<CreateConfig<TContract>["variant"]>,
slot: (() => {
const internalSlot = $internal?.slot;
const userSlot = $user?.slot;
if (!internalSlot && !userSlot) {
return undefined;
}
if (!internalSlot) {
return userSlot;
}
if (!userSlot) {
return internalSlot;
}
// Combine slots by merging What objects for each slot
const combinedSlot: any = {};
const allSlotKeys = new Set([
...Object.keys(internalSlot),
...Object.keys(userSlot),
]);
for (const slotKey of allSlotKeys) {
combinedSlot[slotKey] = combineWhat(
internalSlot[slotKey as keyof typeof internalSlot],
userSlot[slotKey as keyof typeof userSlot],
);
}
return combinedSlot;
})(),
override: {
...$internal?.override,
...$user?.override,
} as Partial<CreateConfig<TContract>["override"]>,
token: {
...$internal?.token,
...$user?.token,
} as Partial<CreateConfig<TContract>["token"]>,
});
}