Skip to content
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
RobinMalfait authored and adamwathan committed Sep 6, 2024
commit 237c6ded5026c2e8d7a94c60f969c5507479ae7f
136 changes: 129 additions & 7 deletions packages/tailwindcss/src/compat/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,135 @@ describe('theme callbacks', () => {
}),
})
expect(compiler.build(['leading-xl'])).toMatchInlineSnapshot(`
":root {
}
.leading-xl {
line-height: 1.75rem;
}
"
`)
":root {
}
.leading-xl {
line-height: 1.75rem;
}
"
`)
})

test.only('line-heights defined in fontSize tuples in a config take precedence over `@theme default` CSS values (2)', async ({
expect,
}) => {
let input = css`
@theme default {
--color-slate-100: #000100;
--color-slate-200: #000200;
--color-slate-300: #000300;
}
@theme {
--color-slate-400: #100400;
--color-slate-500: #100500;
}
@tailwind utilities;
@config "./config.js";
@plugin "./plugin.js";
`

let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
extend: {
colors: {
slate: {
200: '#200200',
400: '#200400',
600: '#200600',
},
},
hoverColors: ({ theme }) => theme('colors'),
},
},
}),

loadPlugin: async () => {
return plugin(({ matchUtilities, theme }) => {
matchUtilities(
{
'hover-bg': (value) => {
return {
'&:hover': {
backgroundColor: value,
},
}
},
},
{
values: theme('hoverColors'),
},
)
})
},
})
expect(
compiler.build([
'bg-slate-100',
'bg-slate-200',
'bg-slate-300',
'bg-slate-400',
'bg-slate-500',
'bg-slate-600',
'hover-bg-slate-100',
'hover-bg-slate-200',
'hover-bg-slate-300',
'hover-bg-slate-400',
'hover-bg-slate-500',
'hover-bg-slate-600',
]),
).toMatchInlineSnapshot(`
":root {
--color-slate-100: #000100;
--color-slate-300: #000300;
--color-slate-400: #100400;
--color-slate-500: #100500;
}
.bg-slate-100 {
background-color: var(--color-slate-100, #000100);
}
.bg-slate-200 {
background-color: #200200;
}
.bg-slate-300 {
background-color: var(--color-slate-300, #000300);
}
.bg-slate-400 {
background-color: var(--color-slate-400, #100400);
}
.bg-slate-500 {
background-color: var(--color-slate-500, #100500);
}
.bg-slate-600 {
background-color: #200600;
}
.hover-bg-slate-100 {
&:hover {
background-color: #000100;
}
}
.hover-bg-slate-200 {
&:hover {
background-color: #200200;
}
}
.hover-bg-slate-300 {
&:hover {
background-color: #000300;
}
}
.hover-bg-slate-400 {
&:hover {
background-color: #100400;
}
}
.hover-bg-slate-500 {
&:hover {
background-color: #100500;
}
}
"
`)
})
})

Expand Down
13 changes: 10 additions & 3 deletions packages/tailwindcss/src/compat/config/deep-merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export function isPlainObject<T>(value: T): value is T & Record<keyof T, unknown
export function deepMerge<T extends object>(
target: T,
sources: (Partial<T> | null | undefined)[],
customizer: (a: any, b: any) => any,
customizer: (a: any, b: any, keypath: (keyof T)[]) => any,
parentPath: (keyof T)[] = [],
) {
type Key = keyof T
type Value = T[Key]
Expand All @@ -21,14 +22,20 @@ export function deepMerge<T extends object>(
}

for (let k of Reflect.ownKeys(source) as Key[]) {
let merged = customizer(target[k], source[k])
let currentParentPath = [...parentPath, k]
let merged = customizer(target[k], source[k], currentParentPath)

if (merged !== undefined) {
target[k] = merged
} else if (!isPlainObject(target[k]) || !isPlainObject(source[k])) {
target[k] = source[k] as Value
} else {
target[k] = deepMerge({}, [target[k], source[k]], customizer) as Value
target[k] = deepMerge(
{},
[target[k], source[k]],
customizer,
currentParentPath as any,
) as Value
}
}
}
Expand Down
83 changes: 71 additions & 12 deletions packages/tailwindcss/src/compat/plugin-functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DesignSystem } from '../design-system'
import type { Theme, ThemeKey } from '../theme'
import { ThemeOptions, type Theme, type ThemeKey } from '../theme'
import { withAlpha } from '../utilities'
import { DefaultMap } from '../utils/default-map'
import { toKeyPath } from '../utils/to-key-path'
Expand All @@ -24,16 +24,63 @@ export function createThemeFn(

let resolvedValue = (() => {
let keypath = toKeyPath(path)
let cssValue = readFromCss(designSystem.theme, keypath)
let [cssValue, options] = readFromCss(designSystem.theme, keypath)

let configValue = resolveValue(get(configTheme() ?? {}, keypath) ?? null)

if (typeof cssValue !== 'object') {
if (options & ThemeOptions.DEFAULT) {
return configValue ?? cssValue
}

return cssValue
}

let configValue = resolveValue(get(configTheme() ?? {}, keypath) ?? null)

if (configValue !== null && typeof configValue === 'object' && !Array.isArray(configValue)) {
return deepMerge({}, [configValue, cssValue], (_, b) => b)
let configValueCopy = deepMerge({}, [configValue], (_, b) => {
return b
})

for (let key in cssValue) {
let configLeafValue = get(configValueCopy, key.split('-'))
if (configLeafValue && options.get(key) & ThemeOptions.DEFAULT) {
continue
}

configValueCopy[key] = cssValue[key]
}

console.dir({ configValueCopy }, { depth: null })

return configValueCopy
// console.dir({ abc }, { depth: null })
return deepMerge({}, [configValue, cssValue], (a, b, path) => {
// console.log({
// a,
// b,
// path,
// cssValue,
// options: cssValue === null ? options : options.get(path.join('-')),
// })
// let fullKey = path.join('-')
// // console.log({ fullKey, options, configValue })
// if (
// configValue[fullKey] &&
// options.get(fullKey.slice('--color-'.length) & ThemeOptions.DEFAULT)
// ) {
// return configValue[fullKey]
// }
//
// console.log({ a, b })
// if (typeof a === 'string' && typeof b === 'string') {
// let key = path.join('-')
// console.log({ a, b, key })
// if (options.get(key.slice('--color-'.length)) & ThemeOptions.DEFAULT) {
// return a
// }
// }
return b
})
}

// Values from CSS take precedence over values from the config
Expand All @@ -49,11 +96,14 @@ export function createThemeFn(
}
}

function readFromCss(theme: Theme, path: string[]) {
function readFromCss(
theme: Theme,
path: string[],
): [value: string | null, options: number] | [value: object, options: Map<string, number>] {
// `--color-red-500` should resolve to the theme variable directly, no look up
// and handling of nested objects is required.
if (path.length === 1 && path[0].startsWith('--')) {
return theme.get([path[0] as ThemeKey])
return [theme.get([path[0] as ThemeKey]), theme.getOptions(path[0])]
}

type ThemeValue =
Expand Down Expand Up @@ -82,10 +132,13 @@ function readFromCss(theme: Theme, path: string[]) {
let map = new Map<string | null, ThemeValue>()
let nested = new DefaultMap<string | null, Map<string, string>>(() => new Map())

let ns = theme.namespace(`--${themeKey}` as any)
// TODO: Bad Robin
if (themeKey === 'colors') themeKey = 'color'

let ns = theme.namespace(`--${themeKey}` as any)
if (ns.size === 0) {
return null
console.log({ themeKey })
return [null, ThemeOptions.NONE]
}

for (let [key, value] of ns) {
Expand Down Expand Up @@ -117,6 +170,12 @@ function readFromCss(theme: Theme, path: string[]) {
// We have to turn the map into object-like structure for v3 compatibility
let obj: Record<string, unknown> = {}
let useNestedObjects = false // paths.some((path) => nestedKeys.has(path))
let options = new Map(
Array.from(ns.entries()).map(([key]) => {
let fullKey = key === null ? `--${themeKey}` : `--${themeKey}-${key}`
return [key, theme.getOptions(fullKey)]
}),
)

for (let [key, value] of map) {
key = key ?? 'DEFAULT'
Expand All @@ -139,17 +198,17 @@ function readFromCss(theme: Theme, path: string[]) {
// the `DEFAULT` key from the list of possible values. If there is no
// `DEFAULT` in the list, there is no match so return `null`.
if (path[path.length - 1] === 'DEFAULT') {
return obj?.DEFAULT ?? null
return [obj?.DEFAULT ?? null, options.get(null) ?? 0]
}

// The request looked like `theme('animation.spin')` and was turned into a
// lookup for `--animation-spin-*` which had only one entry which means it
// should be returned directly.
if ('DEFAULT' in obj && Object.keys(obj).length === 1) {
return obj.DEFAULT
return [obj.DEFAULT, options.get(null) ?? 0]
}

return obj
return [obj, options]
}

function get(obj: any, path: string[]) {
Expand Down
4 changes: 4 additions & 0 deletions packages/tailwindcss/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class Theme {
return ((this.values.get(key)?.options ?? 0) & ThemeOptions.DEFAULT) === ThemeOptions.DEFAULT
}

getOptions(key: string) {
return this.values.get(key)?.options ?? 0
}

entries() {
return this.values.entries()
}
Expand Down