tailwindcss 3.0.24 → 3.1.0
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 +57 -3
- package/colors.d.ts +3 -0
- package/defaultConfig.d.ts +3 -0
- package/defaultTheme.d.ts +3 -0
- package/lib/cli-peer-dependencies.js +8 -3
- package/lib/cli.js +118 -77
- package/lib/corePluginList.js +1 -0
- package/lib/corePlugins.js +146 -117
- package/lib/css/preflight.css +1 -8
- package/lib/featureFlags.js +8 -6
- package/lib/index.js +10 -13
- package/lib/lib/cacheInvalidation.js +32 -14
- package/lib/lib/collapseAdjacentRules.js +5 -3
- package/lib/lib/defaultExtractor.js +191 -32
- package/lib/lib/evaluateTailwindFunctions.js +22 -13
- package/lib/lib/expandApplyAtRules.js +232 -195
- package/lib/lib/expandTailwindAtRules.js +40 -26
- package/lib/lib/generateRules.js +106 -42
- package/lib/lib/regex.js +52 -0
- package/lib/lib/resolveDefaultsAtRules.js +6 -9
- package/lib/lib/setupContextUtils.js +131 -79
- package/lib/lib/setupTrackingContext.js +7 -9
- package/lib/lib/sharedState.js +1 -2
- package/lib/lib/substituteScreenAtRules.js +1 -2
- package/lib/postcss-plugins/nesting/plugin.js +1 -2
- package/lib/util/buildMediaQuery.js +1 -2
- package/lib/util/cloneDeep.js +2 -4
- package/lib/util/color.js +26 -36
- package/lib/util/createPlugin.js +1 -2
- package/lib/util/createUtilityPlugin.js +1 -2
- package/lib/util/dataTypes.js +14 -12
- package/lib/util/flattenColorPalette.js +2 -5
- package/lib/util/formatVariantSelector.js +64 -57
- package/lib/util/getAllConfigs.js +10 -5
- package/lib/util/isValidArbitraryValue.js +1 -2
- package/lib/util/log.js +2 -3
- package/lib/util/negateValue.js +1 -2
- package/lib/util/normalizeConfig.js +33 -23
- package/lib/util/normalizeScreens.js +1 -2
- package/lib/util/parseAnimationValue.js +1 -2
- package/lib/util/parseBoxShadowValue.js +2 -43
- package/lib/util/pluginUtils.js +11 -3
- package/lib/util/resolveConfig.js +57 -34
- package/lib/util/splitAtTopLevelOnly.js +90 -0
- package/lib/util/transformThemeValue.js +4 -2
- package/lib/util/validateConfig.js +21 -0
- package/lib/util/withAlphaVariable.js +5 -5
- package/package.json +21 -16
- package/peers/index.js +3264 -1330
- package/plugin.d.ts +11 -0
- package/src/cli-peer-dependencies.js +7 -1
- package/src/cli.js +97 -33
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +57 -40
- package/src/css/preflight.css +1 -8
- package/src/featureFlags.js +2 -2
- package/src/index.js +0 -2
- package/src/lib/collapseAdjacentRules.js +5 -1
- package/src/lib/defaultExtractor.js +177 -35
- package/src/lib/evaluateTailwindFunctions.js +20 -4
- package/src/lib/expandApplyAtRules.js +247 -188
- package/src/lib/expandTailwindAtRules.js +4 -4
- package/src/lib/generateRules.js +69 -5
- package/src/lib/regex.js +74 -0
- package/src/lib/resolveDefaultsAtRules.js +1 -1
- package/src/lib/setupContextUtils.js +103 -39
- package/src/lib/setupTrackingContext.js +4 -0
- package/src/util/color.js +20 -18
- package/src/util/dataTypes.js +11 -5
- package/src/util/formatVariantSelector.js +79 -62
- package/src/util/getAllConfigs.js +7 -0
- package/src/util/log.js +1 -1
- package/src/util/normalizeConfig.js +0 -8
- package/src/util/parseBoxShadowValue.js +3 -50
- package/src/util/pluginUtils.js +13 -1
- package/src/util/resolveConfig.js +66 -54
- package/src/util/splitAtTopLevelOnly.js +71 -0
- package/src/util/toPath.js +1 -1
- package/src/util/transformThemeValue.js +4 -2
- package/src/util/validateConfig.js +13 -0
- package/src/util/withAlphaVariable.js +1 -1
- package/stubs/defaultConfig.stub.js +2 -3
- package/stubs/simpleConfig.stub.js +1 -0
- package/types/config.d.ts +325 -0
- package/types/generated/.gitkeep +0 -0
- package/types/generated/colors.d.ts +276 -0
- package/types/generated/corePluginList.d.ts +1 -0
- package/types/index.d.ts +1 -0
package/src/lib/regex.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
|
|
2
|
+
const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string|RegExp|Array<string|RegExp>} source
|
|
6
|
+
*/
|
|
7
|
+
function toSource(source) {
|
|
8
|
+
source = Array.isArray(source) ? source : [source]
|
|
9
|
+
|
|
10
|
+
source = source.map((item) => (item instanceof RegExp ? item.source : item))
|
|
11
|
+
|
|
12
|
+
return source.join('')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string|RegExp|Array<string|RegExp>} source
|
|
17
|
+
*/
|
|
18
|
+
export function pattern(source) {
|
|
19
|
+
return new RegExp(toSource(source), 'g')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string|RegExp|Array<string|RegExp>} source
|
|
24
|
+
*/
|
|
25
|
+
export function withoutCapturing(source) {
|
|
26
|
+
return new RegExp(`(?:${toSource(source)})`, 'g')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Array<string|RegExp>} sources
|
|
31
|
+
*/
|
|
32
|
+
export function any(sources) {
|
|
33
|
+
return `(?:${sources.map(toSource).join('|')})`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string|RegExp} source
|
|
38
|
+
*/
|
|
39
|
+
export function optional(source) {
|
|
40
|
+
return `(?:${toSource(source)})?`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string|RegExp|Array<string|RegExp>} source
|
|
45
|
+
*/
|
|
46
|
+
export function zeroOrMore(source) {
|
|
47
|
+
return `(?:${toSource(source)})*`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a RegExp that matches balanced brackets for a given depth
|
|
52
|
+
* We have to specify a depth because JS doesn't support recursive groups using ?R
|
|
53
|
+
*
|
|
54
|
+
* Based on https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java/17759264#17759264
|
|
55
|
+
*
|
|
56
|
+
* @param {string|RegExp|Array<string|RegExp>} source
|
|
57
|
+
*/
|
|
58
|
+
export function nestedBrackets(open, close, depth = 1) {
|
|
59
|
+
return withoutCapturing([
|
|
60
|
+
escape(open),
|
|
61
|
+
/[^\s]*/,
|
|
62
|
+
depth === 1
|
|
63
|
+
? `[^${escape(open)}${escape(close)}\s]*`
|
|
64
|
+
: any([`[^${escape(open)}${escape(close)}\s]*`, nestedBrackets(open, close, depth - 1)]),
|
|
65
|
+
/[^\s]*/,
|
|
66
|
+
escape(close),
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function escape(string) {
|
|
71
|
+
return string && REGEX_HAS_SPECIAL.test(string)
|
|
72
|
+
? string.replace(REGEX_SPECIAL, '\\$&')
|
|
73
|
+
: string || ''
|
|
74
|
+
}
|
|
@@ -134,7 +134,7 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
|
134
134
|
source: universal.source,
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
-
universalRule.selectors = ['*', '::before', '::after']
|
|
137
|
+
universalRule.selectors = ['*', '::before', '::after', '::backdrop']
|
|
138
138
|
|
|
139
139
|
universalRule.append(universal.nodes)
|
|
140
140
|
universal.before(universalRule)
|
|
@@ -4,6 +4,7 @@ import postcss from 'postcss'
|
|
|
4
4
|
import dlv from 'dlv'
|
|
5
5
|
import selectorParser from 'postcss-selector-parser'
|
|
6
6
|
|
|
7
|
+
import { flagEnabled } from '../featureFlags.js'
|
|
7
8
|
import transformThemeValue from '../util/transformThemeValue'
|
|
8
9
|
import parseObjectStyles from '../util/parseObjectStyles'
|
|
9
10
|
import prefixSelector from '../util/prefixSelector'
|
|
@@ -22,6 +23,8 @@ import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
|
22
23
|
import { generateRules } from './generateRules'
|
|
23
24
|
import { hasContentChanged } from './cacheInvalidation.js'
|
|
24
25
|
|
|
26
|
+
let MATCH_VARIANT = Symbol()
|
|
27
|
+
|
|
25
28
|
function prefix(context, selector) {
|
|
26
29
|
let prefix = context.tailwindConfig.prefix
|
|
27
30
|
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
|
@@ -170,6 +173,34 @@ function withIdentifiers(styles) {
|
|
|
170
173
|
})
|
|
171
174
|
}
|
|
172
175
|
|
|
176
|
+
export function isValidVariantFormatString(format) {
|
|
177
|
+
return format.startsWith('@') || format.includes('&')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function parseVariant(variant) {
|
|
181
|
+
variant = variant
|
|
182
|
+
.replace(/\n+/g, '')
|
|
183
|
+
.replace(/\s{1,}/g, ' ')
|
|
184
|
+
.trim()
|
|
185
|
+
|
|
186
|
+
let fns = parseVariantFormatString(variant)
|
|
187
|
+
.map((str) => {
|
|
188
|
+
if (!str.startsWith('@')) {
|
|
189
|
+
return ({ format }) => format(str)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str)
|
|
193
|
+
return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() }))
|
|
194
|
+
})
|
|
195
|
+
.reverse()
|
|
196
|
+
|
|
197
|
+
return (api) => {
|
|
198
|
+
for (let fn of fns) {
|
|
199
|
+
fn(api)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
173
204
|
function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
|
|
174
205
|
function getConfigValue(path, defaultValue) {
|
|
175
206
|
return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
|
|
@@ -191,51 +222,25 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
191
222
|
return context.tailwindConfig.prefix + identifier
|
|
192
223
|
}
|
|
193
224
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return ({ modifySelectors, container, separator }) => {
|
|
200
|
-
return variantFunction({ modifySelectors, container, separator })
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
variantFunction = variantFunction
|
|
205
|
-
.replace(/\n+/g, '')
|
|
206
|
-
.replace(/\s{1,}/g, ' ')
|
|
207
|
-
.trim()
|
|
208
|
-
|
|
209
|
-
let fns = parseVariantFormatString(variantFunction)
|
|
210
|
-
.map((str) => {
|
|
211
|
-
if (!str.startsWith('@')) {
|
|
212
|
-
return ({ format }) => format(str)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
let [, name, params] = /@(.*?) (.*)/g.exec(str)
|
|
216
|
-
return ({ wrap }) => wrap(postcss.atRule({ name, params }))
|
|
217
|
-
})
|
|
218
|
-
.reverse()
|
|
225
|
+
function resolveThemeValue(path, defaultValue, opts = {}) {
|
|
226
|
+
const [pathRoot, ...subPaths] = toPath(path)
|
|
227
|
+
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
|
|
228
|
+
return transformThemeValue(pathRoot)(value, opts)
|
|
229
|
+
}
|
|
219
230
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
const theme = Object.assign(
|
|
232
|
+
(path, defaultValue = undefined) => resolveThemeValue(path, defaultValue),
|
|
233
|
+
{
|
|
234
|
+
withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, { opacityValue }),
|
|
235
|
+
}
|
|
236
|
+
)
|
|
226
237
|
|
|
227
|
-
|
|
228
|
-
variantMap.set(variantName, variantFunctions)
|
|
229
|
-
},
|
|
238
|
+
let api = {
|
|
230
239
|
postcss,
|
|
231
240
|
prefix: applyConfiguredPrefix,
|
|
232
241
|
e: escapeClassName,
|
|
233
242
|
config: getConfigValue,
|
|
234
|
-
theme
|
|
235
|
-
const [pathRoot, ...subPaths] = toPath(path)
|
|
236
|
-
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
|
|
237
|
-
return transformThemeValue(pathRoot)(value)
|
|
238
|
-
},
|
|
243
|
+
theme,
|
|
239
244
|
corePlugins: (path) => {
|
|
240
245
|
if (Array.isArray(tailwindConfig.corePlugins)) {
|
|
241
246
|
return tailwindConfig.corePlugins.includes(path)
|
|
@@ -440,7 +445,65 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
440
445
|
context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
|
|
441
446
|
}
|
|
442
447
|
},
|
|
448
|
+
addVariant(variantName, variantFunctions, options = {}) {
|
|
449
|
+
variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
|
|
450
|
+
if (typeof variantFunction !== 'string') {
|
|
451
|
+
// Safelist public API functions
|
|
452
|
+
return (api) => {
|
|
453
|
+
let { args, modifySelectors, container, separator, wrap, format } = api
|
|
454
|
+
let result = variantFunction(
|
|
455
|
+
Object.assign(
|
|
456
|
+
{ modifySelectors, container, separator },
|
|
457
|
+
variantFunction[MATCH_VARIANT] && { args, wrap, format }
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if (typeof result === 'string' && !isValidVariantFormatString(result)) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (Array.isArray(result)) {
|
|
468
|
+
return result.map((variant) => parseVariant(variant))
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// result may be undefined with legacy variants that use APIs like `modifySelectors`
|
|
472
|
+
return result && parseVariant(result)(api)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!isValidVariantFormatString(variantFunction)) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return parseVariant(variantFunction)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
insertInto(variantList, variantName, options)
|
|
486
|
+
variantMap.set(variantName, variantFunctions)
|
|
487
|
+
},
|
|
443
488
|
}
|
|
489
|
+
|
|
490
|
+
if (flagEnabled(tailwindConfig, 'matchVariant')) {
|
|
491
|
+
api.matchVariant = function (variants, options) {
|
|
492
|
+
for (let variant in variants) {
|
|
493
|
+
for (let [k, v] of Object.entries(options?.values ?? {})) {
|
|
494
|
+
api.addVariant(`${variant}-${k}`, variants[variant](v))
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
api.addVariant(
|
|
498
|
+
variant,
|
|
499
|
+
Object.assign(({ args }) => variants[variant](args), { [MATCH_VARIANT]: true }),
|
|
500
|
+
options
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return api
|
|
444
507
|
}
|
|
445
508
|
|
|
446
509
|
let fileModifiedMapCache = new WeakMap()
|
|
@@ -560,6 +623,7 @@ function resolvePlugins(context, root) {
|
|
|
560
623
|
let afterVariants = [
|
|
561
624
|
variantPlugins['directionVariants'],
|
|
562
625
|
variantPlugins['reducedMotionVariants'],
|
|
626
|
+
variantPlugins['prefersContrastVariants'],
|
|
563
627
|
variantPlugins['darkVariants'],
|
|
564
628
|
variantPlugins['printVariant'],
|
|
565
629
|
variantPlugins['screenVariants'],
|
|
@@ -16,6 +16,7 @@ import { env } from './sharedState'
|
|
|
16
16
|
|
|
17
17
|
import { getContext, getFileModifiedMap } from './setupContextUtils'
|
|
18
18
|
import parseDependency from '../util/parseDependency'
|
|
19
|
+
import { validateConfig } from '../util/validateConfig.js'
|
|
19
20
|
|
|
20
21
|
let configPathCache = new LRU({ maxSize: 100 })
|
|
21
22
|
|
|
@@ -63,6 +64,7 @@ function getTailwindConfig(configOrPath) {
|
|
|
63
64
|
delete require.cache[file]
|
|
64
65
|
}
|
|
65
66
|
let newConfig = resolveConfig(require(userConfigPath))
|
|
67
|
+
newConfig = validateConfig(newConfig)
|
|
66
68
|
let newHash = hash(newConfig)
|
|
67
69
|
configPathCache.set(userConfigPath, [newConfig, newHash, newDeps, newModified])
|
|
68
70
|
return [newConfig, userConfigPath, newHash, newDeps]
|
|
@@ -73,6 +75,8 @@ function getTailwindConfig(configOrPath) {
|
|
|
73
75
|
configOrPath.config === undefined ? configOrPath : configOrPath.config
|
|
74
76
|
)
|
|
75
77
|
|
|
78
|
+
newConfig = validateConfig(newConfig)
|
|
79
|
+
|
|
76
80
|
return [newConfig, null, hash(newConfig), []]
|
|
77
81
|
}
|
|
78
82
|
|
package/src/util/color.js
CHANGED
|
@@ -8,13 +8,15 @@ let ALPHA_SEP = /\s*[,/]\s*/
|
|
|
8
8
|
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/
|
|
9
9
|
|
|
10
10
|
let RGB = new RegExp(
|
|
11
|
-
`^
|
|
11
|
+
`^(rgb)a?\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
|
|
12
12
|
)
|
|
13
13
|
let HSL = new RegExp(
|
|
14
|
-
`^
|
|
14
|
+
`^(hsl)a?\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// In "loose" mode the color may contain fewer than 3 parts, as long as at least
|
|
18
|
+
// one of the parts is variable.
|
|
19
|
+
export function parseColor(value, { loose = false } = {}) {
|
|
18
20
|
if (typeof value !== 'string') {
|
|
19
21
|
return null
|
|
20
22
|
}
|
|
@@ -42,27 +44,27 @@ export function parseColor(value) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
let
|
|
47
|
+
let match = value.match(RGB) ?? value.match(HSL)
|
|
46
48
|
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
49
|
-
mode: 'rgb',
|
|
50
|
-
color: [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((v) => v.toString()),
|
|
51
|
-
alpha: rgbMatch[4]?.toString?.(),
|
|
52
|
-
}
|
|
49
|
+
if (match === null) {
|
|
50
|
+
return null
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
let
|
|
53
|
+
let color = [match[2], match[3], match[4]].filter(Boolean).map((v) => v.toString())
|
|
56
54
|
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
59
|
-
mode: 'hsl',
|
|
60
|
-
color: [hslMatch[1], hslMatch[2], hslMatch[3]].map((v) => v.toString()),
|
|
61
|
-
alpha: hslMatch[4]?.toString?.(),
|
|
62
|
-
}
|
|
55
|
+
if (!loose && color.length !== 3) {
|
|
56
|
+
return null
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
if (color.length < 3 && !color.some((part) => /^var\(.*?\)$/.test(part))) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
mode: match[1],
|
|
65
|
+
color,
|
|
66
|
+
alpha: match[5]?.toString?.(),
|
|
67
|
+
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
export function formatColor({ mode, color, alpha }) {
|
package/src/util/dataTypes.js
CHANGED
|
@@ -42,10 +42,16 @@ export function normalize(value, isRoot = true) {
|
|
|
42
42
|
|
|
43
43
|
// Add spaces around operators inside calc() that do not follow an operator
|
|
44
44
|
// or '('.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
value = value.replace(/calc\(.+\)/g, (match) => {
|
|
46
|
+
return match.replace(
|
|
47
|
+
/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
|
|
48
|
+
'$1 $2 '
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Add spaces around some operators not inside calc() that do not follow an operator
|
|
53
|
+
// or '('.
|
|
54
|
+
return value.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([\/])/g, '$1 $2 ')
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export function url(value) {
|
|
@@ -115,7 +121,7 @@ export function color(value) {
|
|
|
115
121
|
part = normalize(part)
|
|
116
122
|
|
|
117
123
|
if (part.startsWith('var(')) return true
|
|
118
|
-
if (parseColor(part) !== null) return colors++, true
|
|
124
|
+
if (parseColor(part, { loose: true }) !== null) return colors++, true
|
|
119
125
|
|
|
120
126
|
return false
|
|
121
127
|
})
|
|
@@ -29,18 +29,26 @@ export function formatVariantSelector(current, ...others) {
|
|
|
29
29
|
return current
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function finalizeSelector(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
export function finalizeSelector(
|
|
33
|
+
format,
|
|
34
|
+
{
|
|
35
|
+
selector,
|
|
36
|
+
candidate,
|
|
37
|
+
context,
|
|
38
|
+
|
|
39
|
+
// Split by the separator, but ignore the separator inside square brackets:
|
|
40
|
+
//
|
|
41
|
+
// E.g.: dark:lg:hover:[paint-order:markers]
|
|
42
|
+
// ┬ ┬ ┬ ┬
|
|
43
|
+
// │ │ │ ╰── We will not split here
|
|
44
|
+
// ╰──┴─────┴─────────────── We will split here
|
|
45
|
+
//
|
|
46
|
+
base = candidate
|
|
47
|
+
.split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
|
|
48
|
+
.pop(),
|
|
49
|
+
}
|
|
50
|
+
) {
|
|
51
|
+
let ast = selectorParser().astSync(selector)
|
|
44
52
|
|
|
45
53
|
if (context?.tailwindConfig?.prefix) {
|
|
46
54
|
format = prefixSelector(context.tailwindConfig.prefix, format)
|
|
@@ -48,6 +56,19 @@ export function finalizeSelector(format, { selector, candidate, context }) {
|
|
|
48
56
|
|
|
49
57
|
format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
|
|
50
58
|
|
|
59
|
+
let formatAst = selectorParser().astSync(format)
|
|
60
|
+
|
|
61
|
+
// Remove extraneous selectors that do not include the base class/candidate being matched against
|
|
62
|
+
// For example if we have a utility defined `.a, .b { color: red}`
|
|
63
|
+
// And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
|
|
64
|
+
ast.each((node) => {
|
|
65
|
+
let hasClassesMatchingCandidate = node.some((n) => n.type === 'class' && n.value === base)
|
|
66
|
+
|
|
67
|
+
if (!hasClassesMatchingCandidate) {
|
|
68
|
+
node.remove()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
51
72
|
// Normalize escaped classes, e.g.:
|
|
52
73
|
//
|
|
53
74
|
// The idea would be to replace the escaped `base` in the selector with the
|
|
@@ -59,65 +80,61 @@ export function finalizeSelector(format, { selector, candidate, context }) {
|
|
|
59
80
|
// base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
|
|
60
81
|
// escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
|
|
61
82
|
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return node
|
|
69
|
-
})
|
|
70
|
-
}).processSync(selector)
|
|
83
|
+
ast.walkClasses((node) => {
|
|
84
|
+
if (node.raws && node.value.includes(base)) {
|
|
85
|
+
node.raws.value = escapeClassName(unescape(node.raws.value))
|
|
86
|
+
}
|
|
87
|
+
})
|
|
71
88
|
|
|
72
89
|
// We can safely replace the escaped base now, since the `base` section is
|
|
73
90
|
// now in a normalized escaped value.
|
|
74
|
-
|
|
91
|
+
ast.walkClasses((node) => {
|
|
92
|
+
if (node.value === base) {
|
|
93
|
+
node.replaceWith(...formatAst.nodes)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
75
96
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
93
|
-
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
94
|
-
//
|
|
95
|
-
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
|
|
96
|
-
function collectPseudoElements(selector) {
|
|
97
|
-
let nodes = []
|
|
98
|
-
|
|
99
|
-
for (let node of selector.nodes) {
|
|
100
|
-
if (isPseudoElement(node)) {
|
|
101
|
-
nodes.push(node)
|
|
102
|
-
selector.removeChild(node)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (node?.nodes) {
|
|
106
|
-
nodes.push(...collectPseudoElements(node))
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return nodes
|
|
97
|
+
// This will make sure to move pseudo's to the correct spot (the end for
|
|
98
|
+
// pseudo elements) because otherwise the selector will never work
|
|
99
|
+
// anyway.
|
|
100
|
+
//
|
|
101
|
+
// E.g.:
|
|
102
|
+
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
103
|
+
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
104
|
+
//
|
|
105
|
+
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
|
|
106
|
+
function collectPseudoElements(selector) {
|
|
107
|
+
let nodes = []
|
|
108
|
+
|
|
109
|
+
for (let node of selector.nodes) {
|
|
110
|
+
if (isPseudoElement(node)) {
|
|
111
|
+
nodes.push(node)
|
|
112
|
+
selector.removeChild(node)
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
selector.nodes.push(pseudoElements.sort(sortSelector))
|
|
115
|
+
if (node?.nodes) {
|
|
116
|
+
nodes.push(...collectPseudoElements(node))
|
|
116
117
|
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return nodes
|
|
121
|
+
}
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
// Remove unnecessary pseudo selectors that we used as placeholders
|
|
124
|
+
ast.each((selector) => {
|
|
125
|
+
selector.walkPseudos((p) => {
|
|
126
|
+
if (selectorFunctions.has(p.value)) {
|
|
127
|
+
p.replaceWith(p.nodes)
|
|
128
|
+
}
|
|
119
129
|
})
|
|
120
|
-
|
|
130
|
+
|
|
131
|
+
let pseudoElements = collectPseudoElements(selector)
|
|
132
|
+
if (pseudoElements.length > 0) {
|
|
133
|
+
selector.nodes.push(pseudoElements.sort(sortSelector))
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return ast.toString()
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
// Note: As a rule, double colons (::) should be used instead of a single colon
|
|
@@ -9,6 +9,13 @@ export default function getAllConfigs(config) {
|
|
|
9
9
|
|
|
10
10
|
const features = {
|
|
11
11
|
// Add experimental configs here...
|
|
12
|
+
respectDefaultRingColorOpacity: {
|
|
13
|
+
theme: {
|
|
14
|
+
ringColor: {
|
|
15
|
+
DEFAULT: '#3b82f67f',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
const experimentals = Object.keys(features)
|
package/src/util/log.js
CHANGED
|
@@ -3,7 +3,7 @@ import colors from 'picocolors'
|
|
|
3
3
|
let alreadyShown = new Set()
|
|
4
4
|
|
|
5
5
|
function log(type, messages, key) {
|
|
6
|
-
if (process.env.JEST_WORKER_ID
|
|
6
|
+
if (typeof process !== 'undefined' && process.env.JEST_WORKER_ID) return
|
|
7
7
|
|
|
8
8
|
if (key && alreadyShown.has(key)) return
|
|
9
9
|
if (key) alreadyShown.add(key)
|
|
@@ -258,13 +258,5 @@ export function normalizeConfig(config) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
if (config.content.files.length === 0) {
|
|
262
|
-
log.warn('content-problems', [
|
|
263
|
-
'The `content` option in your Tailwind CSS configuration is missing or empty.',
|
|
264
|
-
'Configure your content sources or your generated CSS will be missing styles.',
|
|
265
|
-
'https://tailwindcss.com/docs/content-configuration',
|
|
266
|
-
])
|
|
267
|
-
}
|
|
268
|
-
|
|
269
261
|
return config
|
|
270
262
|
}
|
|
@@ -1,58 +1,11 @@
|
|
|
1
|
+
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
|
|
2
|
+
|
|
1
3
|
let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
|
|
2
4
|
let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
|
|
3
5
|
let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
|
|
4
6
|
|
|
5
|
-
let SPECIALS = /[(),]/g
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This splits a string on top-level commas.
|
|
9
|
-
*
|
|
10
|
-
* Regex doesn't support recursion (at least not the JS-flavored version).
|
|
11
|
-
* So we have to use a tiny state machine to keep track of paren vs comma
|
|
12
|
-
* placement. Before we'd only exclude commas from the inner-most nested
|
|
13
|
-
* set of parens rather than any commas that were not contained in parens
|
|
14
|
-
* at all which is the intended behavior here.
|
|
15
|
-
*
|
|
16
|
-
* Expected behavior:
|
|
17
|
-
* var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)
|
|
18
|
-
* ─┬─ ┬ ┬ ┬
|
|
19
|
-
* x x x ╰──────── Split because top-level
|
|
20
|
-
* ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
|
|
21
|
-
*
|
|
22
|
-
* @param {string} input
|
|
23
|
-
*/
|
|
24
|
-
function* splitByTopLevelCommas(input) {
|
|
25
|
-
SPECIALS.lastIndex = -1
|
|
26
|
-
|
|
27
|
-
let depth = 0
|
|
28
|
-
let lastIndex = 0
|
|
29
|
-
let found = false
|
|
30
|
-
|
|
31
|
-
// Find all parens & commas
|
|
32
|
-
// And only split on commas if they're top-level
|
|
33
|
-
for (let match of input.matchAll(SPECIALS)) {
|
|
34
|
-
if (match[0] === '(') depth++
|
|
35
|
-
if (match[0] === ')') depth--
|
|
36
|
-
if (match[0] === ',' && depth === 0) {
|
|
37
|
-
found = true
|
|
38
|
-
|
|
39
|
-
yield input.substring(lastIndex, match.index)
|
|
40
|
-
lastIndex = match.index + match[0].length
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Provide the last segment of the string if available
|
|
45
|
-
// Otherwise the whole string since no commas were found
|
|
46
|
-
// This mirrors the behavior of string.split()
|
|
47
|
-
if (found) {
|
|
48
|
-
yield input.substring(lastIndex)
|
|
49
|
-
} else {
|
|
50
|
-
yield input
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
7
|
export function parseBoxShadowValue(input) {
|
|
55
|
-
let shadows = Array.from(
|
|
8
|
+
let shadows = Array.from(splitAtTopLevelOnly(input, ','))
|
|
56
9
|
return shadows.map((shadow) => {
|
|
57
10
|
let value = shadow.trim()
|
|
58
11
|
let result = { raw: value }
|