Skip to content

Commit

Permalink
Merge pull request #505 from dcastil/feature/504/export-config-extens…
Browse files Browse the repository at this point in the history
…ion-type

Export ConfigExtension type from package
  • Loading branch information
dcastil authored Dec 21, 2024
2 parents f65b4eb + b450bbd commit a7b7ad1
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 41 deletions.
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

0 comments on commit a7b7ad1

Please sign in to comment.