Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export ConfigExtension type from package #505

Merged
merged 4 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { twMerge } from './lib/tw-merge'
export {
type ClassValidator,
type Config,
type ConfigExtension,
type DefaultClassGroupIds,
type DefaultThemeGroupIds,
type ExperimentalParseClassNameParam,
Expand Down
28 changes: 14 additions & 14 deletions src/lib/class-group-utils.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import {
AnyClassGroupIds,
AnyConfig,
AnyThemeGroupIds,
ClassGroup,
ClassValidator,
Config,
GenericClassGroupIds,
GenericConfig,
GenericThemeGroupIds,
ThemeGetter,
ThemeObject,
} from './types'

export interface ClassPartObject {
nextPart: Map<string, ClassPartObject>
validators: ClassValidatorObject[]
classGroupId?: GenericClassGroupIds
classGroupId?: AnyClassGroupIds
}

interface ClassValidatorObject {
classGroupId: GenericClassGroupIds
classGroupId: AnyClassGroupIds
validator: ClassValidator
}

const CLASS_PART_SEPARATOR = '-'

export const createClassGroupUtils = (config: GenericConfig) => {
export const createClassGroupUtils = (config: AnyConfig) => {
const classMap = createClassMap(config)
const { conflictingClassGroups, conflictingClassGroupModifiers } = config

Expand All @@ -38,7 +38,7 @@ export const createClassGroupUtils = (config: GenericConfig) => {
}

const getConflictingClassGroupIds = (
classGroupId: GenericClassGroupIds,
classGroupId: AnyClassGroupIds,
hasPostfixModifier: boolean,
) => {
const conflicts = conflictingClassGroups[classGroupId] || []
Expand All @@ -59,7 +59,7 @@ export const createClassGroupUtils = (config: GenericConfig) => {
const getGroupRecursive = (
classParts: string[],
classPartObject: ClassPartObject,
): GenericClassGroupIds | undefined => {
): AnyClassGroupIds | undefined => {
if (classParts.length === 0) {
return classPartObject.classGroupId
}
Expand Down Expand Up @@ -103,7 +103,7 @@ const getGroupIdForArbitraryProperty = (className: string) => {
/**
* Exported for testing only
*/
export const createClassMap = (config: Config<GenericClassGroupIds, GenericThemeGroupIds>) => {
export const createClassMap = (config: Config<AnyClassGroupIds, AnyThemeGroupIds>) => {
const { theme, prefix } = config
const classMap: ClassPartObject = {
nextPart: new Map<string, ClassPartObject>(),
Expand All @@ -123,10 +123,10 @@ export const createClassMap = (config: Config<GenericClassGroupIds, GenericTheme
}

const processClassesRecursively = (
classGroup: ClassGroup<GenericThemeGroupIds>,
classGroup: ClassGroup<AnyThemeGroupIds>,
classPartObject: ClassPartObject,
classGroupId: GenericClassGroupIds,
theme: ThemeObject<GenericThemeGroupIds>,
classGroupId: AnyClassGroupIds,
theme: ThemeObject<AnyThemeGroupIds>,
) => {
classGroup.forEach((classDefinition) => {
if (typeof classDefinition === 'string') {
Expand Down Expand Up @@ -187,9 +187,9 @@ const isThemeGetter = (func: ClassValidator | ThemeGetter): func is ThemeGetter
(func as ThemeGetter).isThemeGetter

const getPrefixedClassGroupEntries = (
classGroupEntries: Array<[classGroupId: string, classGroup: ClassGroup<GenericThemeGroupIds>]>,
classGroupEntries: Array<[classGroupId: string, classGroup: ClassGroup<AnyThemeGroupIds>]>,
prefix: string | undefined,
): Array<[classGroupId: string, classGroup: ClassGroup<GenericThemeGroupIds>]> => {
): Array<[classGroupId: string, classGroup: ClassGroup<AnyThemeGroupIds>]> => {
if (!prefix) {
return classGroupEntries
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/config-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createClassGroupUtils } from './class-group-utils'
import { createLruCache } from './lru-cache'
import { createParseClassName } from './parse-class-name'
import { GenericConfig } from './types'
import { AnyConfig } from './types'

export type ConfigUtils = ReturnType<typeof createConfigUtils>

export const createConfigUtils = (config: GenericConfig) => ({
export const createConfigUtils = (config: AnyConfig) => ({
cache: createLruCache<string, string>(config.cacheSize),
parseClassName: createParseClassName(config),
...createClassGroupUtils(config),
Expand Down
8 changes: 4 additions & 4 deletions src/lib/create-tailwind-merge.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createConfigUtils } from './config-utils'
import { mergeClassList } from './merge-classlist'
import { ClassNameValue, twJoin } from './tw-join'
import { GenericConfig } from './types'
import { AnyConfig } from './types'

type CreateConfigFirst = () => GenericConfig
type CreateConfigSubsequent = (config: GenericConfig) => GenericConfig
type CreateConfigFirst = () => AnyConfig
type CreateConfigSubsequent = (config: AnyConfig) => AnyConfig
type TailwindMerge = (...classLists: ClassNameValue[]) => string
type ConfigUtils = ReturnType<typeof createConfigUtils>

Expand All @@ -20,7 +20,7 @@ export function createTailwindMerge(
function initTailwindMerge(classList: string) {
const config = createConfigRest.reduce(
(previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig),
createConfigFirst() as GenericConfig,
createConfigFirst() as AnyConfig,
)

configUtils = createConfigUtils(config)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/extend-tailwind-merge.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createTailwindMerge } from './create-tailwind-merge'
import { getDefaultConfig } from './default-config'
import { mergeConfigs } from './merge-configs'
import { ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds, GenericConfig } from './types'
import { AnyConfig, ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds } from './types'

type CreateConfigSubsequent = (config: GenericConfig) => GenericConfig
type CreateConfigSubsequent = (config: AnyConfig) => AnyConfig

export const extendTailwindMerge = <
AdditionalClassGroupIds extends string = never,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/merge-configs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ConfigExtension, GenericConfig } from './types'
import { AnyConfig, ConfigExtension } from './types'

/**
* @param baseConfig Config where other config will be merged into. This object will be mutated.
* @param configExtension Partial config to merge into the `baseConfig`.
*/
export const mergeConfigs = <ClassGroupIds extends string, ThemeGroupIds extends string = never>(
baseConfig: GenericConfig,
baseConfig: AnyConfig,
{
cacheSize,
prefix,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/parse-class-name.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GenericConfig } from './types'
import { AnyConfig } from './types'

export const IMPORTANT_MODIFIER = '!'

export const createParseClassName = (config: GenericConfig) => {
export const createParseClassName = (config: AnyConfig) => {
const { separator, experimentalParseClassName } = config
const isSeparatorSingleCharacter = separator.length === 1
const firstSeparatorCharacter = separator[0]
Expand Down
48 changes: 36 additions & 12 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/**
* Type the tailwind-merge configuration adheres to.
*/
export interface Config<ClassGroupIds extends string, ThemeGroupIds extends string>
extends ConfigStatic,
ConfigGroups<ClassGroupIds, ThemeGroupIds> {}
extends ConfigStaticPart,
ConfigGroupsPart<ClassGroupIds, ThemeGroupIds> {}

interface ConfigStatic {
/**
* The static part of the tailwind-merge configuration. When merging multiple configurations, the properties of this interface are always overridden.
*/
interface ConfigStaticPart {
/**
* Integer indicating size of LRU cache used for memoizing results.
* - Cache might be up to twice as big as `cacheSize`
Expand Down Expand Up @@ -75,7 +81,10 @@ export interface ExperimentalParsedClassName {
maybePostfixModifierPosition: number | undefined
}

interface ConfigGroups<ClassGroupIds extends string, ThemeGroupIds extends string> {
/**
* The dynamic part of the tailwind-merge configuration. When merging multiple configurations, the user can choose to either override or extend the properties of this interface.
*/
interface ConfigGroupsPart<ClassGroupIds extends string, ThemeGroupIds extends string> {
/**
* Theme scales used in classGroups.
* The keys are the same as in the Tailwind config but the values are sometimes defined more broadly.
Expand Down Expand Up @@ -109,10 +118,13 @@ interface ConfigGroups<ClassGroupIds extends string, ThemeGroupIds extends strin
>
}

/**
* Type of the configuration object that can be passed to `extendTailwindMerge`.
*/
export interface ConfigExtension<ClassGroupIds extends string, ThemeGroupIds extends string>
extends Partial<ConfigStatic> {
override?: PartialPartial<ConfigGroups<ClassGroupIds, ThemeGroupIds>>
extend?: PartialPartial<ConfigGroups<ClassGroupIds, ThemeGroupIds>>
extends Partial<ConfigStaticPart> {
override?: PartialPartial<ConfigGroupsPart<ClassGroupIds, ThemeGroupIds>>
extend?: PartialPartial<ConfigGroupsPart<ClassGroupIds, ThemeGroupIds>>
}

type PartialPartial<T> = {
Expand All @@ -131,18 +143,24 @@ type ClassDefinition<ThemeGroupIds extends string> =
| ClassObject<ThemeGroupIds>
export type ClassValidator = (classPart: string) => boolean
export interface ThemeGetter {
(theme: ThemeObject<GenericThemeGroupIds>): ClassGroup<GenericClassGroupIds>
(theme: ThemeObject<AnyThemeGroupIds>): ClassGroup<AnyClassGroupIds>
isThemeGetter: true
}
type ClassObject<ThemeGroupIds extends string> = Record<
string,
readonly ClassDefinition<ThemeGroupIds>[]
>

// Hack from https://stackoverflow.com/questions/56687668/a-way-to-disable-type-argument-inference-in-generics/56688073#56688073
/**
* Hack from https://stackoverflow.com/questions/56687668/a-way-to-disable-type-argument-inference-in-generics/56688073#56688073
*
* Could be replaced with NoInfer utility type from TypeScript (https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype), but that is only supported in TypeScript 5.4 or higher, so I should wait some time before using it.
*/
export type NoInfer<T> = [T][T extends any ? 0 : never]

/**
* Theme group IDs included in the default configuration of tailwind-merge.
*
* If you want to use a scale that is not supported in the `ThemeObject` type,
* consider using `classGroups` instead of `theme`.
*
Expand Down Expand Up @@ -176,6 +194,9 @@ export type DefaultThemeGroupIds =
| 'spacing'
| 'translate'

/**
* Class group IDs included in the default configuration of tailwind-merge.
*/
export type DefaultClassGroupIds =
| 'accent'
| 'align-content'
Expand Down Expand Up @@ -458,7 +479,10 @@ export type DefaultClassGroupIds =
| 'will-change'
| 'z'

export type GenericClassGroupIds = string
export type GenericThemeGroupIds = string
export type AnyClassGroupIds = string
export type AnyThemeGroupIds = string

export type GenericConfig = Config<GenericClassGroupIds, GenericThemeGroupIds>
/**
* type of the tailwind-merge configuration that allows for any possible configuration.
*/
export type AnyConfig = Config<AnyClassGroupIds, AnyThemeGroupIds>
5 changes: 4 additions & 1 deletion tests/public-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ClassNameValue,
ClassValidator,
Config,
ConfigExtension,
DefaultClassGroupIds,
DefaultThemeGroupIds,
createTailwindMerge,
Expand Down Expand Up @@ -183,7 +184,9 @@ test('mergeConfigs has correct inputs and outputs', () => {
})

test('extendTailwindMerge has correct inputs and outputs', () => {
expect(extendTailwindMerge({})).toStrictEqual(expect.any(Function))
expect(extendTailwindMerge({} satisfies ConfigExtension<string, string>)).toStrictEqual(
expect.any(Function),
)
})

test('fromTheme has correct inputs and outputs', () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/type-generics.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, test } from 'vitest'

import { extendTailwindMerge, fromTheme, getDefaultConfig, mergeConfigs } from '../src'
import { GenericConfig } from '../src/lib/types'
import { AnyConfig } from '../src/lib/types'

test('extendTailwindMerge type generics work correctly', () => {
const tailwindMerge1 = extendTailwindMerge({
Expand Down Expand Up @@ -69,7 +69,7 @@ test('extendTailwindMerge type generics work correctly', () => {

expect(tailwindMerge2('')).toBe('')

const tailwindMerge3 = extendTailwindMerge((v: GenericConfig) => v, getDefaultConfig)
const tailwindMerge3 = extendTailwindMerge((v: AnyConfig) => v, getDefaultConfig)

expect(tailwindMerge3('')).toBe('')
})
Expand Down
Loading