UNPKG

@use-pico/cls

Version:

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

134 lines (122 loc) 3.23 kB
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"]>, }); }