Skip to content

Commit 7642751

Browse files
Improve compatibility with special default values in JS configs (#19348)
Fixes #19345 In v3 the `ringColor.DEFAULT` option was used as the default color for ring utilities (when it was defined). This currently gets translated as `--ring-color` but that doesn't work this way in v4. Instead it should translate to `--default-ring-color` and *not* `--ring-color`. I've also tweaked the upgrade tool to handle this properly as well.
1 parent af48117 commit 7642751

File tree

7 files changed

+97
-27
lines changed

7 files changed

+97
-27
lines changed

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970))
1717
- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337))
1818
- Write source maps correctly on the CLI when using `--watch` ([#19373](https://github.com/tailwindlabs/tailwindcss/pull/19373))
19+
- Handle special defaults (like `ringColor.DEFAULT`) in JS configs ([#19348](https://github.com/tailwindlabs/tailwindcss/pull/19348))
1920
- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344))
2021
- Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379))
2122

‎integrations/upgrade/index.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,8 +2347,7 @@ test(
23472347
--shadow-*: initial;
23482348
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
23492349
2350-
--ring-width-*: initial;
2351-
--ring-width: 4px;
2350+
--default-ring-width: 4px;
23522351
23532352
--blur: var(--custom-default-blur);
23542353

‎integrations/upgrade/js-config.test.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ test(
3434
steel: 'rgb(70 130 180 / <alpha-value>)',
3535
smoke: 'rgba(245, 245, 245, var(--smoke-alpha, <alpha-value>))',
3636
},
37+
ringColor: {
38+
DEFAULT: '#c0ffee',
39+
},
3740
opacity: {
3841
superOpaque: '0.95',
3942
},
@@ -191,6 +194,8 @@ test(
191194
--color-steel: rgb(70 130 180);
192195
--color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1));
193196
197+
--default-ring-color: #c0ffee;
198+
194199
--opacity-*: initial;
195200
--opacity-superOpaque: 95%;
196201

‎packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,16 +237,21 @@ async function migrateTheme(
237237
prevSectionKey = sectionKey
238238
}
239239

240-
if (resetNamespaces.has(key[0]) && resetNamespaces.get(key[0]) === false) {
241-
resetNamespaces.set(key[0], true)
242-
let property = keyPathToCssProperty([key[0]])
243-
if (property !== null) {
244-
themeSection.push(` ${escape(`--${property}`)}-*: initial;`)
245-
}
246-
}
247-
248240
let property = keyPathToCssProperty(key)
241+
249242
if (property !== null) {
243+
if (
244+
!property.startsWith('default-') &&
245+
resetNamespaces.has(key[0]) &&
246+
resetNamespaces.get(key[0]) === false
247+
) {
248+
resetNamespaces.set(key[0], true)
249+
let ns = keyPathToCssProperty([key[0]])
250+
if (ns !== null) {
251+
themeSection.push(` ${escape(`--${ns}`)}-*: initial;`)
252+
}
253+
}
254+
250255
themeSection.push(` ${escape(`--${property}`)}: ${value};`)
251256
}
252257
}

‎packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const THEME_KEYS = new Map([
5959
['backdrop-blur-sm', '--backdrop-blur-sm'],
6060
['backdrop-blur-xs', '--backdrop-blur-xs'],
6161

62-
['ring', '--ring-width'],
62+
['ring', '--default-ring-width'],
6363
['ring-3', '--ring-width-3'],
6464
])
6565

‎packages/tailwindcss/src/compat/apply-config-to-theme.test.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ test('config values can be merged into the theme', () => {
3131
normal: '0 1px 3px black',
3232
},
3333

34+
borderWidth: {
35+
DEFAULT: '1.5px',
36+
},
37+
outlineWidth: {
38+
DEFAULT: '2.5px',
39+
},
40+
ringWidth: {
41+
DEFAULT: '3.5px',
42+
},
43+
3444
borderRadius: {
3545
sm: '0.33rem',
3646
},
@@ -57,6 +67,10 @@ test('config values can be merged into the theme', () => {
5767
'2xl': ['2rem'],
5868
},
5969

70+
ringColor: {
71+
DEFAULT: '#fff',
72+
},
73+
6074
letterSpacing: {
6175
superWide: '0.25em',
6276
},
@@ -77,8 +91,12 @@ test('config values can be merged into the theme', () => {
7791
},
7892

7993
transitionTimingFunction: {
94+
DEFAULT: 'ease-in-out',
8095
fast: 'cubic-bezier(0, 0.55, 0.45, 1)',
8196
},
97+
transitionDuration: {
98+
DEFAULT: '1234ms',
99+
},
82100
},
83101
},
84102
base: '/root',
@@ -125,6 +143,23 @@ test('config values can be merged into the theme', () => {
125143
expect(theme.resolve('100%', ['--width'])).toEqual('100%')
126144
expect(theme.resolve('9xs', ['--container'])).toEqual('6rem')
127145
expect(theme.resolve('fast', ['--ease'])).toEqual('cubic-bezier(0, 0.55, 0.45, 1)')
146+
147+
expect(theme.get(['--border'])).toEqual(null)
148+
expect(theme.get(['--default-border-width'])).toEqual('1.5px')
149+
150+
expect(theme.get(['--outline'])).toEqual(null)
151+
expect(theme.get(['--default-outline-width'])).toEqual('2.5px')
152+
153+
expect(theme.get(['--ring-color'])).toEqual(null)
154+
expect(theme.get(['--default-ring-color'])).toEqual('#fff')
155+
156+
expect(theme.get(['--ring-width'])).toEqual(null)
157+
expect(theme.get(['--default-ring-width'])).toEqual('3.5px')
158+
159+
expect(theme.get(['--default-transition-duration'])).toEqual('1234ms')
160+
161+
expect(theme.get(['--ease'])).toEqual(null)
162+
expect(theme.get(['--default-transition-timing-function'])).toEqual('ease-in-out')
128163
})
129164

130165
test('will reset default theme values with overwriting theme values', () => {

‎packages/tailwindcss/src/compat/apply-config-to-theme.ts‎

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,32 +147,57 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk
147147
return toAdd
148148
}
149149

150+
const SPECIAL_DEFAULT_KEYS: Record<string, string> = {
151+
borderWidth: 'border-width',
152+
outlineWidth: 'outline-width',
153+
ringColor: 'ring-color',
154+
ringWidth: 'ring-width',
155+
transitionDuration: 'transition-duration',
156+
transitionTimingFunction: 'transition-timing-function',
157+
}
158+
159+
const OLD_TO_NEW_NAMESPACE: Record<string, string> = {
160+
animation: 'animate',
161+
aspectRatio: 'aspect',
162+
borderRadius: 'radius',
163+
boxShadow: 'shadow',
164+
colors: 'color',
165+
containers: 'container',
166+
fontFamily: 'font',
167+
fontSize: 'text',
168+
letterSpacing: 'tracking',
169+
lineHeight: 'leading',
170+
maxWidth: 'container',
171+
screens: 'breakpoint',
172+
transitionTimingFunction: 'ease',
173+
}
174+
150175
const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/
151176

152177
export function keyPathToCssProperty(path: string[]) {
178+
// In some special cases the `DEFAULT` key did not map to a "default" utility
179+
// e.g. `ringColor.DEFAULT` wasn't *just* used for `ring`. It was used for
180+
// all ring utilities as the color when one wasn't specified.
181+
//
182+
// We place these specialty values under the `--default-*` namespace to signal
183+
// that they are defaults used by (potentially) multiple utilities.
184+
let specialDefault = SPECIAL_DEFAULT_KEYS[path[0]]
185+
if (specialDefault && path[1] === 'DEFAULT') return `default-${specialDefault}`
186+
153187
// The legacy container component config should not be included in the Theme
154188
if (path[0] === 'container') return null
155189

156-
path = path.slice()
157-
158-
if (path[0] === 'animation') path[0] = 'animate'
159-
if (path[0] === 'aspectRatio') path[0] = 'aspect'
160-
if (path[0] === 'borderRadius') path[0] = 'radius'
161-
if (path[0] === 'boxShadow') path[0] = 'shadow'
162-
if (path[0] === 'colors') path[0] = 'color'
163-
if (path[0] === 'containers') path[0] = 'container'
164-
if (path[0] === 'fontFamily') path[0] = 'font'
165-
if (path[0] === 'fontSize') path[0] = 'text'
166-
if (path[0] === 'letterSpacing') path[0] = 'tracking'
167-
if (path[0] === 'lineHeight') path[0] = 'leading'
168-
if (path[0] === 'maxWidth') path[0] = 'container'
169-
if (path[0] === 'screens') path[0] = 'breakpoint'
170-
if (path[0] === 'transitionTimingFunction') path[0] = 'ease'
171-
172190
for (let part of path) {
173191
if (!IS_VALID_KEY.test(part)) return null
174192
}
175193

194+
// Map old v3 namespaces to new theme namespaces
195+
let ns = OLD_TO_NEW_NAMESPACE[path[0]]
196+
if (ns) {
197+
path = path.slice()
198+
path[0] = ns
199+
}
200+
176201
return (
177202
path
178203
// [1] should move into the nested object tuple. To create the CSS variable

0 commit comments

Comments
 (0)