diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index 4e79b818ea08..4e9104bacf01 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -5,6 +5,8 @@ import { isValidArbitrary } from './utils/is-valid-arbitrary' import { segment } from './utils/segment' import * as ValueParser from './value-parser' import { walk, WalkAction } from './walk' +import { normalizeEscapedKey } from './utils/escape' + const COLON = 0x3a const DASH = 0x2d @@ -12,6 +14,8 @@ const LOWER_A = 0x61 const LOWER_Z = 0x7a const IS_VALID_NAMED_VALUE = /^[a-zA-Z0-9_.%-]+$/ + + export type ArbitraryUtilityValue = { kind: 'arbitrary' @@ -76,16 +80,13 @@ export type ArbitraryModifier = { value: string } -export type NamedModifier = { - kind: 'named' +let candidate = normalizeEscapedKey(match[1]) - /** - * ``` - * bg-red-500/50 - * ^^ - * ``` - */ - value: string +export type NamedModifier = { + candidate.value = { + kind: 'named', + value: normalizeEscapedKey(value), + fraction, } export type CandidateModifier = ArbitraryModifier | NamedModifier diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 073fb242f1e0..b347e284e625 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -302,3 +302,8 @@ export class Theme { } export type ThemeKey = `--${string}` + + +export function normalizeEscapedKey(key: string) { + return key.replace(/\\(.)/g, '$1') +} diff --git a/packages/tailwindcss/src/utils/escape.ts b/packages/tailwindcss/src/utils/escape.ts index cbcfa62a0b42..b0f5c48f0f7f 100644 --- a/packages/tailwindcss/src/utils/escape.ts +++ b/packages/tailwindcss/src/utils/escape.ts @@ -79,3 +79,8 @@ export function unescape(escaped: string) { : match[1] }) } + + +export function normalizeEscapedKey(key: string) { + return key.replace(/\\(.)/g, '$1') +} diff --git a/packages/tailwindcss/src/utils/parseThemeUtil.ts b/packages/tailwindcss/src/utils/parseThemeUtil.ts new file mode 100644 index 000000000000..e07d3570e19f --- /dev/null +++ b/packages/tailwindcss/src/utils/parseThemeUtil.ts @@ -0,0 +1,4 @@ +import { normalizeEscapedKey } from './escape' + +let normalizedKey = normalizeEscapedKey(key) +return theme[normalizedKey] ?? theme[key] diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index 0c3534efd748..3cb8026f77cd 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -5,6 +5,7 @@ import path from 'node:path' import { optimize } from '../../@tailwindcss-node/src/optimize' import { compile } from '../src' import { segment } from '../src/utils/segment' +import { resolveConfig } from '../src/compat/config/resolve-config' const html = String.raw const css = String.raw @@ -2258,3 +2259,16 @@ async function getPropertyValue( [selector, property] as const, ) } + + +test('supports escaped characters in theme keys', () => { + let config = resolveConfig({ + theme: { + colors: { + 'brand\\:primary': '#ff0000', + }, + }, + }) + + expect(config.theme.colors['brand\\:primary']).toBe('#ff0000') +}) diff --git a/packages/tailwindcss/tests/utilities.test.ts b/packages/tailwindcss/tests/utilities.test.ts new file mode 100644 index 000000000000..bb500bd23ddc --- /dev/null +++ b/packages/tailwindcss/tests/utilities.test.ts @@ -0,0 +1,20 @@ +test('generates utilities for escaped theme keys', async () => { + let css = await run( + '@tailwind utilities;', + { + theme: { + colors: { + 'brand\\:primary': '#ff0000', + }, + }, + content: [ + { + raw: '
', + }, + ], + } + ) + + expect(css).toContain('.bg-brand\\:primary') + expect(css).toContain('background-color: #ff0000') +})