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.
Files changed (88) hide show
  1. package/CHANGELOG.md +57 -3
  2. package/colors.d.ts +3 -0
  3. package/defaultConfig.d.ts +3 -0
  4. package/defaultTheme.d.ts +3 -0
  5. package/lib/cli-peer-dependencies.js +8 -3
  6. package/lib/cli.js +118 -77
  7. package/lib/corePluginList.js +1 -0
  8. package/lib/corePlugins.js +146 -117
  9. package/lib/css/preflight.css +1 -8
  10. package/lib/featureFlags.js +8 -6
  11. package/lib/index.js +10 -13
  12. package/lib/lib/cacheInvalidation.js +32 -14
  13. package/lib/lib/collapseAdjacentRules.js +5 -3
  14. package/lib/lib/defaultExtractor.js +191 -32
  15. package/lib/lib/evaluateTailwindFunctions.js +22 -13
  16. package/lib/lib/expandApplyAtRules.js +232 -195
  17. package/lib/lib/expandTailwindAtRules.js +40 -26
  18. package/lib/lib/generateRules.js +106 -42
  19. package/lib/lib/regex.js +52 -0
  20. package/lib/lib/resolveDefaultsAtRules.js +6 -9
  21. package/lib/lib/setupContextUtils.js +131 -79
  22. package/lib/lib/setupTrackingContext.js +7 -9
  23. package/lib/lib/sharedState.js +1 -2
  24. package/lib/lib/substituteScreenAtRules.js +1 -2
  25. package/lib/postcss-plugins/nesting/plugin.js +1 -2
  26. package/lib/util/buildMediaQuery.js +1 -2
  27. package/lib/util/cloneDeep.js +2 -4
  28. package/lib/util/color.js +26 -36
  29. package/lib/util/createPlugin.js +1 -2
  30. package/lib/util/createUtilityPlugin.js +1 -2
  31. package/lib/util/dataTypes.js +14 -12
  32. package/lib/util/flattenColorPalette.js +2 -5
  33. package/lib/util/formatVariantSelector.js +64 -57
  34. package/lib/util/getAllConfigs.js +10 -5
  35. package/lib/util/isValidArbitraryValue.js +1 -2
  36. package/lib/util/log.js +2 -3
  37. package/lib/util/negateValue.js +1 -2
  38. package/lib/util/normalizeConfig.js +33 -23
  39. package/lib/util/normalizeScreens.js +1 -2
  40. package/lib/util/parseAnimationValue.js +1 -2
  41. package/lib/util/parseBoxShadowValue.js +2 -43
  42. package/lib/util/pluginUtils.js +11 -3
  43. package/lib/util/resolveConfig.js +57 -34
  44. package/lib/util/splitAtTopLevelOnly.js +90 -0
  45. package/lib/util/transformThemeValue.js +4 -2
  46. package/lib/util/validateConfig.js +21 -0
  47. package/lib/util/withAlphaVariable.js +5 -5
  48. package/package.json +21 -16
  49. package/peers/index.js +3264 -1330
  50. package/plugin.d.ts +11 -0
  51. package/src/cli-peer-dependencies.js +7 -1
  52. package/src/cli.js +97 -33
  53. package/src/corePluginList.js +1 -1
  54. package/src/corePlugins.js +57 -40
  55. package/src/css/preflight.css +1 -8
  56. package/src/featureFlags.js +2 -2
  57. package/src/index.js +0 -2
  58. package/src/lib/collapseAdjacentRules.js +5 -1
  59. package/src/lib/defaultExtractor.js +177 -35
  60. package/src/lib/evaluateTailwindFunctions.js +20 -4
  61. package/src/lib/expandApplyAtRules.js +247 -188
  62. package/src/lib/expandTailwindAtRules.js +4 -4
  63. package/src/lib/generateRules.js +69 -5
  64. package/src/lib/regex.js +74 -0
  65. package/src/lib/resolveDefaultsAtRules.js +1 -1
  66. package/src/lib/setupContextUtils.js +103 -39
  67. package/src/lib/setupTrackingContext.js +4 -0
  68. package/src/util/color.js +20 -18
  69. package/src/util/dataTypes.js +11 -5
  70. package/src/util/formatVariantSelector.js +79 -62
  71. package/src/util/getAllConfigs.js +7 -0
  72. package/src/util/log.js +1 -1
  73. package/src/util/normalizeConfig.js +0 -8
  74. package/src/util/parseBoxShadowValue.js +3 -50
  75. package/src/util/pluginUtils.js +13 -1
  76. package/src/util/resolveConfig.js +66 -54
  77. package/src/util/splitAtTopLevelOnly.js +71 -0
  78. package/src/util/toPath.js +1 -1
  79. package/src/util/transformThemeValue.js +4 -2
  80. package/src/util/validateConfig.js +13 -0
  81. package/src/util/withAlphaVariable.js +1 -1
  82. package/stubs/defaultConfig.stub.js +2 -3
  83. package/stubs/simpleConfig.stub.js +1 -0
  84. package/types/config.d.ts +325 -0
  85. package/types/generated/.gitkeep +0 -0
  86. package/types/generated/colors.d.ts +276 -0
  87. package/types/generated/corePluginList.d.ts +1 -0
  88. package/types/index.d.ts +1 -0
@@ -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
- return {
195
- addVariant(variantName, variantFunctions, options = {}) {
196
- variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
197
- if (typeof variantFunction !== 'string') {
198
- // Safelist public API functions
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
- return (api) => {
221
- for (let fn of fns) {
222
- fn(api)
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
- insertInto(variantList, variantName, options)
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(path, defaultValue) {
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
- `^rgba?\\(\\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*\\)$`
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
- `^hsla?\\(\\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*\\)$`
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
- export function parseColor(value) {
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 rgbMatch = value.match(RGB)
47
+ let match = value.match(RGB) ?? value.match(HSL)
46
48
 
47
- if (rgbMatch !== null) {
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 hslMatch = value.match(HSL)
53
+ let color = [match[2], match[3], match[4]].filter(Boolean).map((v) => v.toString())
56
54
 
57
- if (hslMatch !== null) {
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
- return null
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 }) {
@@ -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
- return value.replace(
46
- /(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
47
- '$1 $2 '
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(format, { selector, candidate, context }) {
33
- let separator = context?.tailwindConfig?.separator ?? ':'
34
-
35
- // Split by the separator, but ignore the separator inside square brackets:
36
- //
37
- // E.g.: dark:lg:hover:[paint-order:markers]
38
- // ┬ ┬ ┬ ┬
39
- // │ │ │ ╰── We will not split here
40
- // ╰──┴─────┴─────────────── We will split here
41
- //
42
- let splitter = new RegExp(`\\${separator}(?![^[]*\\])`)
43
- let base = candidate.split(splitter).pop()
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
- selector = selectorParser((selectors) => {
63
- return selectors.walkClasses((node) => {
64
- if (node.raws && node.value.includes(base)) {
65
- node.raws.value = escapeClassName(unescape(node.raws.value))
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
- selector = selector.replace(`.${escapeClassName(base)}`, format)
91
+ ast.walkClasses((node) => {
92
+ if (node.value === base) {
93
+ node.replaceWith(...formatAst.nodes)
94
+ }
95
+ })
75
96
 
76
- // Remove unnecessary pseudo selectors that we used as placeholders
77
- return selectorParser((selectors) => {
78
- return selectors.map((selector) => {
79
- selector.walkPseudos((p) => {
80
- if (selectorFunctions.has(p.value)) {
81
- p.replaceWith(p.nodes)
82
- }
83
-
84
- return p
85
- })
86
-
87
- // This will make sure to move pseudo's to the correct spot (the end for
88
- // pseudo elements) because otherwise the selector will never work
89
- // anyway.
90
- //
91
- // E.g.:
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
- let pseudoElements = collectPseudoElements(selector)
114
- if (pseudoElements.length > 0) {
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
- return selector
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
- }).processSync(selector)
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 !== undefined) return
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(splitByTopLevelCommas(input))
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 }