This commit is contained in:
Jordan Pittman 2024-08-14 12:45:10 -04:00
parent 5111f44efa
commit d8cef56dbd
4 changed files with 76 additions and 26 deletions

View file

@ -48,7 +48,7 @@ export function objectToAst(obj: CssInJs): AstNode[] {
let ast: AstNode[] = []
for (let [name, value] of Object.entries(obj)) {
if (typeof value === 'string') {
if (typeof value !== 'object') {
if (!name.startsWith('--') && value === '@slot') {
ast.push(rule(name, [rule('@slot', [])]))
} else {
@ -56,9 +56,9 @@ export function objectToAst(obj: CssInJs): AstNode[] {
// https://github.com/postcss/postcss-js/blob/b3db658b932b42f6ac14ca0b1d50f50c4569805b/parser.js#L30-L35
name = name.replace(/([A-Z])/g, '-$1').toLowerCase()
ast.push(decl(name, value))
ast.push(decl(name, String(value)))
}
} else {
} else if (value !== null) {
ast.push(rule(name, objectToAst(value)))
}
}

View file

@ -1,9 +1,9 @@
import type { PluginAPI } from '../api'
import type { CorePluginName } from '../generated/core-utils'
import type { PluginUtils } from './utils'
import type { PluginUtils } from './resolve-config'
export type ImportableFile = string | [path: string, options: Record<string, any>]
type PluginAPI = {}
type FilePath = string
type RawFile = { raw: string; extension?: string }
export type RawPreset = Omit<UserConfig, 'content'>
@ -55,7 +55,6 @@ export interface UserConfig {
presets?: Preset[]
plugins?: Plugin[]
theme?: ThemeConfig
corePlugins?: Partial<Record<CorePluginName, boolean>> | CorePluginName[] | boolean
}
export type ThemeValue = ResolvableTo<Record<string, unknown>> | null | undefined
@ -74,5 +73,4 @@ export interface ResolvedConfig {
darkMode: DarkModeConfig
plugins: RawPlugin[]
theme: Record<string, Record<string, unknown>>
corePlugins: Record<CorePluginName, boolean>
}

View file

@ -255,4 +255,44 @@ describe('theme', async () => {
"
`)
})
test('wip', async ({ expect }) => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
`
let compiler = await compile(input, {
loadPlugin: async () => {
return plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{
duration: (value) => ({ 'animation-duration': value }),
},
{
values: theme('animationDuration'),
},
)
},
{
theme: {
extend: {
animationDuration: ({ theme }: { theme: (path: string) => any }) => ({
...theme('transitionDuration'),
}),
},
},
},
)
},
})
expect(compiler.build(['duration-316'])).toMatchInlineSnapshot(`
".duration-316 {
animation-duration: 316ms;
}
"
`)
})
})

View file

@ -3,10 +3,12 @@ import { objectToAst, rule, type AstNode, type CssInJs } from './ast'
import { resolveConfig } from './compat/config/resolve-config'
import type { UserConfig } from './compat/config/types'
import type { DesignSystem } from './design-system'
import type { Theme } from './theme'
import { withAlpha, withNegative } from './utilities'
import { inferDataType } from './utils/infer-data-type'
import { segment } from './utils/segment'
export type Config = UserConfig
export type PluginFn = (api: PluginAPI) => void
export type PluginWithConfig = { handler: PluginFn; config?: UserConfig }
export type PluginWithOptions<T> = {
@ -25,9 +27,7 @@ export type PluginAPI = {
options?: Partial<{
type: string | string[]
supportsNegativeValues: boolean
values: Record<string, string> & {
[BARE_VALUE]?: (value: string) => string | null | undefined
}
values: Record<string, string>
modifiers: 'any' | Record<string, string>
}>,
): void
@ -136,7 +136,7 @@ export function buildPluginApi(
let value: string | null = null
{
let values = options?.values ?? {}
let values: Record<string, string> = options?.values ?? {}
if (isColor) {
// Color utilities implicitly support `inherit`, `transparent`, and `currentColor`
@ -156,11 +156,17 @@ export function buildPluginApi(
} else if (values[candidate.value.value]) {
value = values[candidate.value.value]
} else if (values[BARE_VALUE]) {
value = values[BARE_VALUE](candidate.value.value) ?? null
// We've snuk the bare value in here as a function even though values are
// typically only ever strings. This is a backwards compatibility hack.
let handleBareValue = values[BARE_VALUE] as unknown as (
value: string,
) => string | null
value = handleBareValue(candidate.value.value) ?? null
}
}
if (!value) return
if (value === null) return
// Resolve the modifier value
let modifier: string | null
@ -182,12 +188,12 @@ export function buildPluginApi(
}
// A modifier was provided but is invalid
if (candidate.modifier && !modifier) {
if (candidate.modifier && modifier !== null) {
// For arbitrary values, return `null` to avoid falling through to the next utility
return candidate.value?.kind === 'arbitrary' ? null : undefined
}
if (isColor && modifier) {
if (isColor && modifier !== null) {
value = withAlpha(value, modifier)
}
@ -224,7 +230,10 @@ export function buildPluginApi(
}
}
const BARE_VALUE = Symbol('BARE_VALUE')
// We can't use a symbol here because a lot of code in the ecosystem assumes an object with string / number keys
// for example using Object.entries() + Object.fromEntries() to modify an object which removes symbol keys
// We use `Math.random()` to ensure that users are unable to rely on the name of thiskey
const BARE_VALUE = `__BARE_VALUE__${Math.random().toString(36).slice(2)}`
export function registerPlugins(plugins: Plugin[], designSystem: DesignSystem, ast: AstNode[]) {
let pluginObjects = []
@ -249,7 +258,7 @@ export function registerPlugins(plugins: Plugin[], designSystem: DesignSystem, a
// Now merge all the configs and make all that crap work
let resolvedConfig = resolveConfig([
compatabilityConfig,
createCompatabilityConfig(designSystem.theme),
...pluginObjects.map(({ config }) => config ?? {}),
])
@ -261,14 +270,17 @@ export function registerPlugins(plugins: Plugin[], designSystem: DesignSystem, a
}
}
export const compatabilityConfig = {
theme: {
transitionDuration: {
[BARE_VALUE]: (value: string) => {
if (!Number.isNaN(Number(value))) {
return `${value}ms`
}
export function createCompatabilityConfig(theme: Theme) {
return {
theme: {
transitionDuration: {
DEFAULT: theme.get(['--default-transition-duration']) ?? null,
[BARE_VALUE]: (value: string) => {
if (!Number.isNaN(Number(value))) {
return `${value}ms`
}
},
},
},
},
}
}