tailwindcss 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/lib/corePlugins.js +43 -5
- package/lib/lib/generateRules.js +8 -0
- package/lib/lib/setupContextUtils.js +18 -2
- package/lib/util/dataTypes.js +11 -1
- package/lib/util/pseudoElements.js +3 -0
- package/package.json +1 -1
- package/src/corePlugins.js +44 -5
- package/src/lib/generateRules.js +10 -0
- package/src/lib/setupContextUtils.js +22 -1
- package/src/util/dataTypes.js +12 -0
- package/src/util/pseudoElements.js +4 -0
- package/types/config.d.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
- Nothing yet!
|
|
11
|
+
|
|
12
|
+
## [3.4.1] - 2014-01-05
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Don't remove keyframe stops when using important utilities ([#12639](https://github.com/tailwindlabs/tailwindcss/pull/12639))
|
|
17
|
+
- Don't add spaces to gradients and grid track names when followed by `calc()` ([#12704](https://github.com/tailwindlabs/tailwindcss/pull/12704))
|
|
18
|
+
- Restore old behavior for `class` dark mode strategy ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Add new `selector` and `variant` strategies for dark mode ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Support `rtl` and `ltr` variants on same element as `dir` attribute ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
|
|
27
|
+
|
|
10
28
|
## [3.4.0] - 2023-12-19
|
|
11
29
|
|
|
12
30
|
### Added
|
|
@@ -2333,7 +2351,8 @@ No release notes
|
|
|
2333
2351
|
|
|
2334
2352
|
- Everything!
|
|
2335
2353
|
|
|
2336
|
-
[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.
|
|
2354
|
+
[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.1...HEAD
|
|
2355
|
+
[3.4.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.0...v3.4.1
|
|
2337
2356
|
[3.4.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.7...v3.4.0
|
|
2338
2357
|
[3.3.7]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.6...v3.3.7
|
|
2339
2358
|
[3.3.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.5...v3.3.6
|
package/lib/corePlugins.js
CHANGED
|
@@ -267,15 +267,15 @@ let variantPlugins = {
|
|
|
267
267
|
}
|
|
268
268
|
},
|
|
269
269
|
directionVariants: ({ addVariant })=>{
|
|
270
|
-
addVariant("ltr", '
|
|
271
|
-
addVariant("rtl", '
|
|
270
|
+
addVariant("ltr", '&:where([dir="ltr"], [dir="ltr"] *)');
|
|
271
|
+
addVariant("rtl", '&:where([dir="rtl"], [dir="rtl"] *)');
|
|
272
272
|
},
|
|
273
273
|
reducedMotionVariants: ({ addVariant })=>{
|
|
274
274
|
addVariant("motion-safe", "@media (prefers-reduced-motion: no-preference)");
|
|
275
275
|
addVariant("motion-reduce", "@media (prefers-reduced-motion: reduce)");
|
|
276
276
|
},
|
|
277
277
|
darkVariants: ({ config , addVariant })=>{
|
|
278
|
-
let [mode,
|
|
278
|
+
let [mode, selector = ".dark"] = [].concat(config("darkMode", "media"));
|
|
279
279
|
if (mode === false) {
|
|
280
280
|
mode = "media";
|
|
281
281
|
_log.default.warn("darkmode-false", [
|
|
@@ -284,10 +284,48 @@ let variantPlugins = {
|
|
|
284
284
|
"https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration"
|
|
285
285
|
]);
|
|
286
286
|
}
|
|
287
|
-
if (mode === "
|
|
288
|
-
|
|
287
|
+
if (mode === "variant") {
|
|
288
|
+
let formats;
|
|
289
|
+
if (Array.isArray(selector)) {
|
|
290
|
+
formats = selector;
|
|
291
|
+
} else if (typeof selector === "function") {
|
|
292
|
+
formats = selector;
|
|
293
|
+
} else if (typeof selector === "string") {
|
|
294
|
+
formats = [
|
|
295
|
+
selector
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
// TODO: We could also add these warnings if the user passes a function that returns string | string[]
|
|
299
|
+
// But this is an advanced enough use case that it's probably not necessary
|
|
300
|
+
if (Array.isArray(formats)) {
|
|
301
|
+
for (let format of formats){
|
|
302
|
+
if (format === ".dark") {
|
|
303
|
+
mode = false;
|
|
304
|
+
_log.default.warn("darkmode-variant-without-selector", [
|
|
305
|
+
"When using `variant` for `darkMode`, you must provide a selector.",
|
|
306
|
+
'Example: `darkMode: ["variant", ".your-selector &"]`'
|
|
307
|
+
]);
|
|
308
|
+
} else if (!format.includes("&")) {
|
|
309
|
+
mode = false;
|
|
310
|
+
_log.default.warn("darkmode-variant-without-ampersand", [
|
|
311
|
+
"When using `variant` for `darkMode`, your selector must contain `&`.",
|
|
312
|
+
'Example `darkMode: ["variant", ".your-selector &"]`'
|
|
313
|
+
]);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
selector = formats;
|
|
318
|
+
}
|
|
319
|
+
if (mode === "selector") {
|
|
320
|
+
// New preferred behavior
|
|
321
|
+
addVariant("dark", `&:where(${selector}, ${selector} *)`);
|
|
289
322
|
} else if (mode === "media") {
|
|
290
323
|
addVariant("dark", "@media (prefers-color-scheme: dark)");
|
|
324
|
+
} else if (mode === "variant") {
|
|
325
|
+
addVariant("dark", selector);
|
|
326
|
+
} else if (mode === "class") {
|
|
327
|
+
// Old behavior
|
|
328
|
+
addVariant("dark", `:is(${selector} &)`);
|
|
291
329
|
}
|
|
292
330
|
},
|
|
293
331
|
printVariant: ({ addVariant })=>{
|
package/lib/lib/generateRules.js
CHANGED
|
@@ -162,6 +162,9 @@ function applyImportant(matches, classCandidate) {
|
|
|
162
162
|
return matches;
|
|
163
163
|
}
|
|
164
164
|
let result = [];
|
|
165
|
+
function isInKeyframes(rule) {
|
|
166
|
+
return rule.parent && rule.parent.type === "atrule" && rule.parent.name === "keyframes";
|
|
167
|
+
}
|
|
165
168
|
for (let [meta, rule] of matches){
|
|
166
169
|
let container = _postcss.default.root({
|
|
167
170
|
nodes: [
|
|
@@ -169,6 +172,11 @@ function applyImportant(matches, classCandidate) {
|
|
|
169
172
|
]
|
|
170
173
|
});
|
|
171
174
|
container.walkRules((r)=>{
|
|
175
|
+
// Declarations inside keyframes cannot be marked as important
|
|
176
|
+
// They will be ignored by the browser
|
|
177
|
+
if (isInKeyframes(r)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
172
180
|
let ast = (0, _postcssselectorparser.default)().astSync(r.selector);
|
|
173
181
|
// Remove extraneous selectors that do not include the base candidate
|
|
174
182
|
ast.each((sel)=>(0, _formatVariantSelector.eliminateIrrelevantSelectors)(sel, classCandidate));
|
|
@@ -762,13 +762,29 @@ function resolvePlugins(context, root) {
|
|
|
762
762
|
_corePlugins.variantPlugins["supportsVariants"],
|
|
763
763
|
_corePlugins.variantPlugins["reducedMotionVariants"],
|
|
764
764
|
_corePlugins.variantPlugins["prefersContrastVariants"],
|
|
765
|
-
_corePlugins.variantPlugins["printVariant"],
|
|
766
765
|
_corePlugins.variantPlugins["screenVariants"],
|
|
767
766
|
_corePlugins.variantPlugins["orientationVariants"],
|
|
768
767
|
_corePlugins.variantPlugins["directionVariants"],
|
|
769
768
|
_corePlugins.variantPlugins["darkVariants"],
|
|
770
|
-
_corePlugins.variantPlugins["forcedColorsVariants"]
|
|
769
|
+
_corePlugins.variantPlugins["forcedColorsVariants"],
|
|
770
|
+
_corePlugins.variantPlugins["printVariant"]
|
|
771
771
|
];
|
|
772
|
+
// This is a compatibility fix for the pre 3.4 dark mode behavior
|
|
773
|
+
// `class` retains the old behavior, but `selector` keeps the new behavior
|
|
774
|
+
let isLegacyDarkMode = context.tailwindConfig.darkMode === "class" || Array.isArray(context.tailwindConfig.darkMode) && context.tailwindConfig.darkMode[0] === "class";
|
|
775
|
+
if (isLegacyDarkMode) {
|
|
776
|
+
afterVariants = [
|
|
777
|
+
_corePlugins.variantPlugins["supportsVariants"],
|
|
778
|
+
_corePlugins.variantPlugins["reducedMotionVariants"],
|
|
779
|
+
_corePlugins.variantPlugins["prefersContrastVariants"],
|
|
780
|
+
_corePlugins.variantPlugins["darkVariants"],
|
|
781
|
+
_corePlugins.variantPlugins["screenVariants"],
|
|
782
|
+
_corePlugins.variantPlugins["orientationVariants"],
|
|
783
|
+
_corePlugins.variantPlugins["directionVariants"],
|
|
784
|
+
_corePlugins.variantPlugins["forcedColorsVariants"],
|
|
785
|
+
_corePlugins.variantPlugins["printVariant"]
|
|
786
|
+
];
|
|
787
|
+
}
|
|
772
788
|
return [
|
|
773
789
|
...corePluginList,
|
|
774
790
|
...beforeVariants,
|
package/lib/util/dataTypes.js
CHANGED
|
@@ -140,7 +140,13 @@ function normalize(value, context = null, isRoot = true) {
|
|
|
140
140
|
"keyboard-inset-bottom",
|
|
141
141
|
"keyboard-inset-left",
|
|
142
142
|
"keyboard-inset-width",
|
|
143
|
-
"keyboard-inset-height"
|
|
143
|
+
"keyboard-inset-height",
|
|
144
|
+
"radial-gradient",
|
|
145
|
+
"linear-gradient",
|
|
146
|
+
"conic-gradient",
|
|
147
|
+
"repeating-radial-gradient",
|
|
148
|
+
"repeating-linear-gradient",
|
|
149
|
+
"repeating-conic-gradient"
|
|
144
150
|
];
|
|
145
151
|
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match)=>{
|
|
146
152
|
let result = "";
|
|
@@ -186,6 +192,10 @@ function normalize(value, context = null, isRoot = true) {
|
|
|
186
192
|
result += consumeUntil([
|
|
187
193
|
")"
|
|
188
194
|
]);
|
|
195
|
+
} else if (peek("[")) {
|
|
196
|
+
result += consumeUntil([
|
|
197
|
+
"]"
|
|
198
|
+
]);
|
|
189
199
|
} else if ([
|
|
190
200
|
"+",
|
|
191
201
|
"-",
|
|
@@ -114,6 +114,9 @@ let elementProperties = {
|
|
|
114
114
|
"terminal",
|
|
115
115
|
"jumpable"
|
|
116
116
|
],
|
|
117
|
+
":where": [],
|
|
118
|
+
":is": [],
|
|
119
|
+
":has": [],
|
|
117
120
|
// The default value is used when the pseudo-element is not recognized
|
|
118
121
|
// Because it's not recognized, we don't know if it's terminal or not
|
|
119
122
|
// So we assume it can be moved AND can have user-action pseudo classes attached to it
|
package/package.json
CHANGED
package/src/corePlugins.js
CHANGED
|
@@ -207,8 +207,8 @@ export let variantPlugins = {
|
|
|
207
207
|
},
|
|
208
208
|
|
|
209
209
|
directionVariants: ({ addVariant }) => {
|
|
210
|
-
addVariant('ltr', '
|
|
211
|
-
addVariant('rtl', '
|
|
210
|
+
addVariant('ltr', '&:where([dir="ltr"], [dir="ltr"] *)')
|
|
211
|
+
addVariant('rtl', '&:where([dir="rtl"], [dir="rtl"] *)')
|
|
212
212
|
},
|
|
213
213
|
|
|
214
214
|
reducedMotionVariants: ({ addVariant }) => {
|
|
@@ -217,7 +217,7 @@ export let variantPlugins = {
|
|
|
217
217
|
},
|
|
218
218
|
|
|
219
219
|
darkVariants: ({ config, addVariant }) => {
|
|
220
|
-
let [mode,
|
|
220
|
+
let [mode, selector = '.dark'] = [].concat(config('darkMode', 'media'))
|
|
221
221
|
|
|
222
222
|
if (mode === false) {
|
|
223
223
|
mode = 'media'
|
|
@@ -228,10 +228,49 @@ export let variantPlugins = {
|
|
|
228
228
|
])
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
if (mode === '
|
|
232
|
-
|
|
231
|
+
if (mode === 'variant') {
|
|
232
|
+
let formats
|
|
233
|
+
if (Array.isArray(selector)) {
|
|
234
|
+
formats = selector
|
|
235
|
+
} else if (typeof selector === 'function') {
|
|
236
|
+
formats = selector
|
|
237
|
+
} else if (typeof selector === 'string') {
|
|
238
|
+
formats = [selector]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// TODO: We could also add these warnings if the user passes a function that returns string | string[]
|
|
242
|
+
// But this is an advanced enough use case that it's probably not necessary
|
|
243
|
+
if (Array.isArray(formats)) {
|
|
244
|
+
for (let format of formats) {
|
|
245
|
+
if (format === '.dark') {
|
|
246
|
+
mode = false
|
|
247
|
+
log.warn('darkmode-variant-without-selector', [
|
|
248
|
+
'When using `variant` for `darkMode`, you must provide a selector.',
|
|
249
|
+
'Example: `darkMode: ["variant", ".your-selector &"]`',
|
|
250
|
+
])
|
|
251
|
+
} else if (!format.includes('&')) {
|
|
252
|
+
mode = false
|
|
253
|
+
log.warn('darkmode-variant-without-ampersand', [
|
|
254
|
+
'When using `variant` for `darkMode`, your selector must contain `&`.',
|
|
255
|
+
'Example `darkMode: ["variant", ".your-selector &"]`',
|
|
256
|
+
])
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
selector = formats
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (mode === 'selector') {
|
|
265
|
+
// New preferred behavior
|
|
266
|
+
addVariant('dark', `&:where(${selector}, ${selector} *)`)
|
|
233
267
|
} else if (mode === 'media') {
|
|
234
268
|
addVariant('dark', '@media (prefers-color-scheme: dark)')
|
|
269
|
+
} else if (mode === 'variant') {
|
|
270
|
+
addVariant('dark', selector)
|
|
271
|
+
} else if (mode === 'class') {
|
|
272
|
+
// Old behavior
|
|
273
|
+
addVariant('dark', `:is(${selector} &)`)
|
|
235
274
|
}
|
|
236
275
|
},
|
|
237
276
|
|
package/src/lib/generateRules.js
CHANGED
|
@@ -119,10 +119,20 @@ function applyImportant(matches, classCandidate) {
|
|
|
119
119
|
|
|
120
120
|
let result = []
|
|
121
121
|
|
|
122
|
+
function isInKeyframes(rule) {
|
|
123
|
+
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
|
|
124
|
+
}
|
|
125
|
+
|
|
122
126
|
for (let [meta, rule] of matches) {
|
|
123
127
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
124
128
|
|
|
125
129
|
container.walkRules((r) => {
|
|
130
|
+
// Declarations inside keyframes cannot be marked as important
|
|
131
|
+
// They will be ignored by the browser
|
|
132
|
+
if (isInKeyframes(r)) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
let ast = selectorParser().astSync(r.selector)
|
|
127
137
|
|
|
128
138
|
// Remove extraneous selectors that do not include the base candidate
|
|
@@ -767,14 +767,35 @@ function resolvePlugins(context, root) {
|
|
|
767
767
|
variantPlugins['supportsVariants'],
|
|
768
768
|
variantPlugins['reducedMotionVariants'],
|
|
769
769
|
variantPlugins['prefersContrastVariants'],
|
|
770
|
-
variantPlugins['printVariant'],
|
|
771
770
|
variantPlugins['screenVariants'],
|
|
772
771
|
variantPlugins['orientationVariants'],
|
|
773
772
|
variantPlugins['directionVariants'],
|
|
774
773
|
variantPlugins['darkVariants'],
|
|
775
774
|
variantPlugins['forcedColorsVariants'],
|
|
775
|
+
variantPlugins['printVariant'],
|
|
776
776
|
]
|
|
777
777
|
|
|
778
|
+
// This is a compatibility fix for the pre 3.4 dark mode behavior
|
|
779
|
+
// `class` retains the old behavior, but `selector` keeps the new behavior
|
|
780
|
+
let isLegacyDarkMode =
|
|
781
|
+
context.tailwindConfig.darkMode === 'class' ||
|
|
782
|
+
(Array.isArray(context.tailwindConfig.darkMode) &&
|
|
783
|
+
context.tailwindConfig.darkMode[0] === 'class')
|
|
784
|
+
|
|
785
|
+
if (isLegacyDarkMode) {
|
|
786
|
+
afterVariants = [
|
|
787
|
+
variantPlugins['supportsVariants'],
|
|
788
|
+
variantPlugins['reducedMotionVariants'],
|
|
789
|
+
variantPlugins['prefersContrastVariants'],
|
|
790
|
+
variantPlugins['darkVariants'],
|
|
791
|
+
variantPlugins['screenVariants'],
|
|
792
|
+
variantPlugins['orientationVariants'],
|
|
793
|
+
variantPlugins['directionVariants'],
|
|
794
|
+
variantPlugins['forcedColorsVariants'],
|
|
795
|
+
variantPlugins['printVariant'],
|
|
796
|
+
]
|
|
797
|
+
}
|
|
798
|
+
|
|
778
799
|
return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
|
|
779
800
|
}
|
|
780
801
|
|
package/src/util/dataTypes.js
CHANGED
|
@@ -106,6 +106,13 @@ function normalizeMathOperatorSpacing(value) {
|
|
|
106
106
|
'keyboard-inset-left',
|
|
107
107
|
'keyboard-inset-width',
|
|
108
108
|
'keyboard-inset-height',
|
|
109
|
+
|
|
110
|
+
'radial-gradient',
|
|
111
|
+
'linear-gradient',
|
|
112
|
+
'conic-gradient',
|
|
113
|
+
'repeating-radial-gradient',
|
|
114
|
+
'repeating-linear-gradient',
|
|
115
|
+
'repeating-conic-gradient',
|
|
109
116
|
]
|
|
110
117
|
|
|
111
118
|
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
|
|
@@ -161,6 +168,11 @@ function normalizeMathOperatorSpacing(value) {
|
|
|
161
168
|
result += consumeUntil([')'])
|
|
162
169
|
}
|
|
163
170
|
|
|
171
|
+
// Don't break CSS grid track names
|
|
172
|
+
else if (peek('[')) {
|
|
173
|
+
result += consumeUntil([']'])
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
// Handle operators
|
|
165
177
|
else if (
|
|
166
178
|
['+', '-', '*', '/'].includes(char) &&
|
|
@@ -60,6 +60,10 @@ let elementProperties = {
|
|
|
60
60
|
':first-letter': ['terminal', 'jumpable'],
|
|
61
61
|
':first-line': ['terminal', 'jumpable'],
|
|
62
62
|
|
|
63
|
+
':where': [],
|
|
64
|
+
':is': [],
|
|
65
|
+
':has': [],
|
|
66
|
+
|
|
63
67
|
// The default value is used when the pseudo-element is not recognized
|
|
64
68
|
// Because it's not recognized, we don't know if it's terminal or not
|
|
65
69
|
// So we assume it can be moved AND can have user-action pseudo classes attached to it
|
package/types/config.d.ts
CHANGED
|
@@ -74,6 +74,13 @@ type DarkModeConfig =
|
|
|
74
74
|
| 'class'
|
|
75
75
|
// Use the `class` strategy with a custom class instead of `.dark`.
|
|
76
76
|
| ['class', string]
|
|
77
|
+
// Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior
|
|
78
|
+
| 'selector'
|
|
79
|
+
// Use the `selector` strategy with a custom selector instead of `.dark`.
|
|
80
|
+
| ['selector', string]
|
|
81
|
+
// Use the `variant` strategy, which allows you to completely customize the selector
|
|
82
|
+
// It takes a string or an array of strings, which are passed directly to `addVariant()`
|
|
83
|
+
| ['variant', string | string[]]
|
|
77
84
|
|
|
78
85
|
type Screen = { raw: string } | { min: string } | { max: string } | { min: string; max: string }
|
|
79
86
|
type ScreensConfig = string[] | KeyValuePair<string, string | Screen | Screen[]>
|