tailwindcss 3.0.22 → 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 (119) hide show
  1. package/CHANGELOG.md +92 -2
  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 +10 -5
  6. package/lib/cli.js +266 -203
  7. package/lib/constants.js +8 -8
  8. package/lib/corePluginList.js +1 -0
  9. package/lib/corePlugins.js +1662 -1554
  10. package/lib/css/preflight.css +1 -8
  11. package/lib/featureFlags.js +14 -12
  12. package/lib/index.js +16 -6
  13. package/lib/lib/cacheInvalidation.js +87 -0
  14. package/lib/lib/collapseAdjacentRules.js +30 -15
  15. package/lib/lib/collapseDuplicateDeclarations.js +1 -1
  16. package/lib/lib/defaultExtractor.js +191 -30
  17. package/lib/lib/detectNesting.js +9 -9
  18. package/lib/lib/evaluateTailwindFunctions.js +37 -28
  19. package/lib/lib/expandApplyAtRules.js +379 -189
  20. package/lib/lib/expandTailwindAtRules.js +168 -144
  21. package/lib/lib/generateRules.js +190 -81
  22. package/lib/lib/getModuleDependencies.js +14 -14
  23. package/lib/lib/normalizeTailwindDirectives.js +35 -35
  24. package/lib/lib/partitionApplyAtRules.js +7 -7
  25. package/lib/lib/regex.js +52 -0
  26. package/lib/lib/resolveDefaultsAtRules.js +80 -79
  27. package/lib/lib/setupContextUtils.js +207 -170
  28. package/lib/lib/setupTrackingContext.js +61 -63
  29. package/lib/lib/sharedState.js +11 -8
  30. package/lib/lib/substituteScreenAtRules.js +3 -4
  31. package/lib/postcss-plugins/nesting/README.md +2 -2
  32. package/lib/postcss-plugins/nesting/index.js +1 -1
  33. package/lib/postcss-plugins/nesting/plugin.js +40 -9
  34. package/lib/processTailwindFeatures.js +7 -7
  35. package/lib/public/colors.js +241 -241
  36. package/lib/public/resolve-config.js +5 -5
  37. package/lib/util/buildMediaQuery.js +2 -3
  38. package/lib/util/cloneDeep.js +3 -5
  39. package/lib/util/cloneNodes.js +12 -1
  40. package/lib/util/color.js +42 -51
  41. package/lib/util/createPlugin.js +1 -2
  42. package/lib/util/createUtilityPlugin.js +6 -7
  43. package/lib/util/dataTypes.js +85 -81
  44. package/lib/util/escapeClassName.js +5 -5
  45. package/lib/util/escapeCommas.js +1 -1
  46. package/lib/util/flattenColorPalette.js +4 -7
  47. package/lib/util/formatVariantSelector.js +82 -75
  48. package/lib/util/getAllConfigs.js +15 -10
  49. package/lib/util/hashConfig.js +5 -5
  50. package/lib/util/isKeyframeRule.js +1 -1
  51. package/lib/util/isPlainObject.js +1 -1
  52. package/lib/util/isValidArbitraryValue.js +26 -27
  53. package/lib/util/log.js +9 -10
  54. package/lib/util/nameClass.js +7 -7
  55. package/lib/util/negateValue.js +4 -5
  56. package/lib/util/normalizeConfig.js +68 -58
  57. package/lib/util/normalizeScreens.js +5 -6
  58. package/lib/util/parseAnimationValue.js +56 -57
  59. package/lib/util/parseBoxShadowValue.js +19 -20
  60. package/lib/util/parseDependency.js +32 -32
  61. package/lib/util/parseObjectStyles.js +6 -6
  62. package/lib/util/pluginUtils.js +20 -12
  63. package/lib/util/prefixSelector.js +1 -1
  64. package/lib/util/resolveConfig.js +81 -58
  65. package/lib/util/resolveConfigPath.js +16 -16
  66. package/lib/util/responsive.js +6 -6
  67. package/lib/util/splitAtTopLevelOnly.js +90 -0
  68. package/lib/util/toColorValue.js +1 -1
  69. package/lib/util/toPath.js +2 -2
  70. package/lib/util/transformThemeValue.js +30 -28
  71. package/lib/util/validateConfig.js +21 -0
  72. package/lib/util/withAlphaVariable.js +23 -23
  73. package/package.json +33 -27
  74. package/peers/index.js +7728 -5848
  75. package/plugin.d.ts +11 -0
  76. package/scripts/generate-types.js +52 -0
  77. package/src/cli-peer-dependencies.js +7 -1
  78. package/src/cli.js +118 -24
  79. package/src/corePluginList.js +1 -1
  80. package/src/corePlugins.js +142 -30
  81. package/src/css/preflight.css +1 -8
  82. package/src/featureFlags.js +4 -4
  83. package/src/index.js +15 -1
  84. package/src/lib/cacheInvalidation.js +52 -0
  85. package/src/lib/collapseAdjacentRules.js +21 -2
  86. package/src/lib/defaultExtractor.js +177 -33
  87. package/src/lib/evaluateTailwindFunctions.js +20 -4
  88. package/src/lib/expandApplyAtRules.js +418 -186
  89. package/src/lib/expandTailwindAtRules.js +30 -10
  90. package/src/lib/generateRules.js +142 -51
  91. package/src/lib/regex.js +74 -0
  92. package/src/lib/resolveDefaultsAtRules.js +7 -3
  93. package/src/lib/setupContextUtils.js +142 -87
  94. package/src/lib/setupTrackingContext.js +7 -3
  95. package/src/lib/sharedState.js +2 -0
  96. package/src/postcss-plugins/nesting/README.md +2 -2
  97. package/src/postcss-plugins/nesting/plugin.js +36 -0
  98. package/src/util/cloneNodes.js +14 -1
  99. package/src/util/color.js +25 -21
  100. package/src/util/dataTypes.js +14 -6
  101. package/src/util/formatVariantSelector.js +79 -62
  102. package/src/util/getAllConfigs.js +7 -0
  103. package/src/util/log.js +8 -8
  104. package/src/util/normalizeConfig.js +0 -8
  105. package/src/util/parseBoxShadowValue.js +3 -2
  106. package/src/util/pluginUtils.js +13 -1
  107. package/src/util/resolveConfig.js +66 -22
  108. package/src/util/splitAtTopLevelOnly.js +71 -0
  109. package/src/util/toPath.js +1 -1
  110. package/src/util/transformThemeValue.js +4 -2
  111. package/src/util/validateConfig.js +13 -0
  112. package/src/util/withAlphaVariable.js +1 -1
  113. package/stubs/defaultConfig.stub.js +5 -1
  114. package/stubs/simpleConfig.stub.js +1 -0
  115. package/types/config.d.ts +325 -0
  116. package/types/generated/.gitkeep +0 -0
  117. package/types/generated/colors.d.ts +276 -0
  118. package/types/generated/corePluginList.d.ts +1 -0
  119. package/types/index.d.ts +1 -0
@@ -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'
@@ -20,6 +21,9 @@ import log from '../util/log'
20
21
  import negateValue from '../util/negateValue'
21
22
  import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
23
  import { generateRules } from './generateRules'
24
+ import { hasContentChanged } from './cacheInvalidation.js'
25
+
26
+ let MATCH_VARIANT = Symbol()
23
27
 
24
28
  function prefix(context, selector) {
25
29
  let prefix = context.tailwindConfig.prefix
@@ -84,12 +88,18 @@ function parseStyles(styles) {
84
88
  })
85
89
  }
86
90
 
87
- function getClasses(selector) {
91
+ function getClasses(selector, mutate) {
88
92
  let parser = selectorParser((selectors) => {
89
93
  let allClasses = []
94
+
95
+ if (mutate) {
96
+ mutate(selectors)
97
+ }
98
+
90
99
  selectors.walkClasses((classNode) => {
91
100
  allClasses.push(classNode.value)
92
101
  })
102
+
93
103
  return allClasses
94
104
  })
95
105
  return parser.transformSync(selector)
@@ -100,8 +110,20 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
100
110
 
101
111
  // Handle normal rules
102
112
  if (node.type === 'rule') {
113
+ // Ignore everything inside a :not(...). This allows you to write code like
114
+ // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
115
+ // not generated it. But now we will ignore everything inside a `:not`, so
116
+ // that it still gets generated.
117
+ function ignoreNot(selectors) {
118
+ selectors.walkPseudos((pseudo) => {
119
+ if (pseudo.value === ':not') {
120
+ pseudo.remove()
121
+ }
122
+ })
123
+ }
124
+
103
125
  for (let selector of node.selectors) {
104
- let classCandidates = getClasses(selector)
126
+ let classCandidates = getClasses(selector, ignoreNot)
105
127
  // At least one of the selectors contains non-"on-demandable" candidates.
106
128
  if (classCandidates.length === 0) {
107
129
  state.containsNonOnDemandable = true
@@ -116,9 +138,7 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
116
138
  // Handle at-rules (which contains nested rules)
117
139
  else if (node.type === 'atrule') {
118
140
  node.walkRules((rule) => {
119
- for (let classCandidate of rule.selectors.flatMap((selector) =>
120
- getClasses(selector, state, depth + 1)
121
- )) {
141
+ for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
122
142
  classes.push(classCandidate)
123
143
  }
124
144
  })
@@ -138,7 +158,7 @@ function withIdentifiers(styles) {
138
158
 
139
159
  // If this isn't "on-demandable", assign it a universal candidate to always include it.
140
160
  if (containsNonOnDemandableSelectors) {
141
- candidates.unshift('*')
161
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
142
162
  }
143
163
 
144
164
  // However, it could be that it also contains "on-demandable" candidates.
@@ -153,6 +173,34 @@ function withIdentifiers(styles) {
153
173
  })
154
174
  }
155
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
+
156
204
  function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
157
205
  function getConfigValue(path, defaultValue) {
158
206
  return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
@@ -163,8 +211,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
163
211
  }
164
212
 
165
213
  function prefixIdentifier(identifier, options) {
166
- if (identifier === '*') {
167
- return '*'
214
+ if (identifier === sharedState.NOT_ON_DEMAND) {
215
+ return sharedState.NOT_ON_DEMAND
168
216
  }
169
217
 
170
218
  if (!options.respectPrefix) {
@@ -174,51 +222,25 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
174
222
  return context.tailwindConfig.prefix + identifier
175
223
  }
176
224
 
177
- return {
178
- addVariant(variantName, variantFunctions, options = {}) {
179
- variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
180
- if (typeof variantFunction !== 'string') {
181
- // Safelist public API functions
182
- return ({ modifySelectors, container, separator }) => {
183
- return variantFunction({ modifySelectors, container, separator })
184
- }
185
- }
186
-
187
- variantFunction = variantFunction
188
- .replace(/\n+/g, '')
189
- .replace(/\s{1,}/g, ' ')
190
- .trim()
191
-
192
- let fns = parseVariantFormatString(variantFunction)
193
- .map((str) => {
194
- if (!str.startsWith('@')) {
195
- return ({ format }) => format(str)
196
- }
197
-
198
- let [, name, params] = /@(.*?) (.*)/g.exec(str)
199
- return ({ wrap }) => wrap(postcss.atRule({ name, params }))
200
- })
201
- .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
+ }
202
230
 
203
- return (api) => {
204
- for (let fn of fns) {
205
- fn(api)
206
- }
207
- }
208
- })
231
+ const theme = Object.assign(
232
+ (path, defaultValue = undefined) => resolveThemeValue(path, defaultValue),
233
+ {
234
+ withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, { opacityValue }),
235
+ }
236
+ )
209
237
 
210
- insertInto(variantList, variantName, options)
211
- variantMap.set(variantName, variantFunctions)
212
- },
238
+ let api = {
213
239
  postcss,
214
240
  prefix: applyConfiguredPrefix,
215
241
  e: escapeClassName,
216
242
  config: getConfigValue,
217
- theme(path, defaultValue) {
218
- const [pathRoot, ...subPaths] = toPath(path)
219
- const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
220
- return transformThemeValue(pathRoot)(value)
221
- },
243
+ theme,
222
244
  corePlugins: (path) => {
223
245
  if (Array.isArray(tailwindConfig.corePlugins)) {
224
246
  return tailwindConfig.corePlugins.includes(path)
@@ -230,17 +252,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
252
  // Preserved for backwards compatibility but not used in v3.0+
231
253
  return []
232
254
  },
233
- addUserCss(userCss) {
234
- for (let [identifier, rule] of withIdentifiers(userCss)) {
235
- let offset = offsets.user++
236
-
237
- if (!context.candidateRuleMap.has(identifier)) {
238
- context.candidateRuleMap.set(identifier, [])
239
- }
240
-
241
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
242
- }
243
- },
244
255
  addBase(base) {
245
256
  for (let [identifier, rule] of withIdentifiers(base)) {
246
257
  let prefixedIdentifier = prefixIdentifier(identifier, {})
@@ -434,7 +445,65 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
434
445
  context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
435
446
  }
436
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
+ },
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
+ }
437
504
  }
505
+
506
+ return api
438
507
  }
439
508
 
440
509
  let fileModifiedMapCache = new WeakMap()
@@ -521,15 +590,6 @@ function collectLayerPlugins(root) {
521
590
  }
522
591
  })
523
592
 
524
- root.walkRules((rule) => {
525
- // At this point it is safe to include all the left-over css from the
526
- // user's css file. This is because the `@tailwind` and `@layer` directives
527
- // will already be handled and will be removed from the css tree.
528
- layerPlugins.push(function ({ addUserCss }) {
529
- addUserCss(rule, { respectPrefix: false })
530
- })
531
- })
532
-
533
593
  return layerPlugins
534
594
  }
535
595
 
@@ -563,6 +623,7 @@ function resolvePlugins(context, root) {
563
623
  let afterVariants = [
564
624
  variantPlugins['directionVariants'],
565
625
  variantPlugins['reducedMotionVariants'],
626
+ variantPlugins['prefersContrastVariants'],
566
627
  variantPlugins['darkVariants'],
567
628
  variantPlugins['printVariant'],
568
629
  variantPlugins['screenVariants'],
@@ -744,33 +805,25 @@ function registerPlugins(plugins, context) {
744
805
  // sorting could be weird since you still require them in order to make the
745
806
  // host utitlies work properly. (Thanks Biology)
746
807
  let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')])
747
- context.sortClassList = function sortClassList(classes) {
808
+ context.getClassOrder = function getClassOrder(classes) {
748
809
  let sortedClassNames = new Map()
749
810
  for (let [sort, rule] of generateRules(new Set(classes), context)) {
750
811
  if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue
751
812
  sortedClassNames.set(rule.raws.tailwind.candidate, sort)
752
813
  }
753
814
 
754
- return classes
755
- .map((className) => {
756
- let order = sortedClassNames.get(className) ?? null
815
+ return classes.map((className) => {
816
+ let order = sortedClassNames.get(className) ?? null
757
817
 
758
- if (order === null && parasiteUtilities.has(className)) {
759
- // This will make sure that it is at the very beginning of the
760
- // `components` layer which technically means 'before any
761
- // components'.
762
- order = context.layerOrder.components
763
- }
818
+ if (order === null && parasiteUtilities.has(className)) {
819
+ // This will make sure that it is at the very beginning of the
820
+ // `components` layer which technically means 'before any
821
+ // components'.
822
+ order = context.layerOrder.components
823
+ }
764
824
 
765
- return [className, order]
766
- })
767
- .sort(([, a], [, z]) => {
768
- if (a === z) return 0
769
- if (a === null) return -1
770
- if (z === null) return 1
771
- return bigSign(a - z)
772
- })
773
- .map(([className]) => className)
825
+ return [className, order]
826
+ })
774
827
  }
775
828
 
776
829
  // Generate a list of strings for autocompletion purposes, e.g.
@@ -850,6 +903,8 @@ export function getContext(
850
903
  existingContext = context
851
904
  }
852
905
 
906
+ let cssDidChange = hasContentChanged(sourcePath, root)
907
+
853
908
  // If there's already a context in the cache and we don't need to
854
909
  // reset the context, return the cached context.
855
910
  if (existingContext) {
@@ -857,7 +912,7 @@ export function getContext(
857
912
  [...contextDependencies],
858
913
  getFileModifiedMap(existingContext)
859
914
  )
860
- if (!contextDependenciesChanged) {
915
+ if (!contextDependenciesChanged && !cssDidChange) {
861
916
  return [existingContext, false]
862
917
  }
863
918
  }
@@ -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
 
@@ -112,7 +116,7 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
112
116
  // source path), or set up a new one (including setting up watchers and registering
113
117
  // plugins) then return it
114
118
  export default function setupTrackingContext(configOrPath) {
115
- return ({ tailwindDirectives, registerDependency, applyDirectives }) => {
119
+ return ({ tailwindDirectives, registerDependency }) => {
116
120
  return (root, result) => {
117
121
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
118
122
  getTailwindConfig(configOrPath)
@@ -125,7 +129,7 @@ export default function setupTrackingContext(configOrPath) {
125
129
  // being part of this trigger too, but it's tough because it's impossible
126
130
  // for a layer in one file to end up in the actual @tailwind rule in
127
131
  // another file since independent sources are effectively isolated.
128
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
132
+ if (tailwindDirectives.size > 0) {
129
133
  // Add current css file as a context dependencies.
130
134
  contextDependencies.add(result.opts.from)
131
135
 
@@ -153,7 +157,7 @@ export default function setupTrackingContext(configOrPath) {
153
157
  // We may want to think about `@layer` being part of this trigger too, but it's tough
154
158
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
155
159
  // in another file since independent sources are effectively isolated.
156
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
160
+ if (tailwindDirectives.size > 0) {
157
161
  let fileModifiedMap = getFileModifiedMap(context)
158
162
 
159
163
  // Add template paths as postcss dependencies.
@@ -5,6 +5,8 @@ export const env = {
5
5
  export const contextMap = new Map()
6
6
  export const configContextMap = new Map()
7
7
  export const contextSourcesMap = new Map()
8
+ export const sourceHashMap = new Map()
9
+ export const NOT_ON_DEMAND = new String('*')
8
10
 
9
11
  export function resolveDebug(debug) {
10
12
  if (debug === undefined) {
@@ -1,6 +1,6 @@
1
1
  # tailwindcss/nesting
2
2
 
3
- This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`.
3
+ This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`.
4
4
 
5
5
  Add it to your PostCSS configuration, somewhere before Tailwind itself:
6
6
 
@@ -18,7 +18,7 @@ module.exports = {
18
18
 
19
19
  By default, it uses the [postcss-nested](https://github.com/postcss/postcss-nested) plugin under the hood, which uses a Sass-like syntax and is the plugin that powers nesting support in the [Tailwind CSS plugin API](https://tailwindcss.com/docs/plugins#css-in-js-syntax).
20
20
 
21
- If you'd rather use [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside:
21
+ If you'd rather use [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside:
22
22
 
23
23
  ```shell
24
24
  npm install postcss-nesting
@@ -39,6 +39,42 @@ export function nesting(opts = postcssNested) {
39
39
  decl.remove()
40
40
  })
41
41
 
42
+ /**
43
+ * Use a private PostCSS API to remove the "clean" flag from the entire AST.
44
+ * This is done because running process() on the AST will set the "clean"
45
+ * flag on all nodes, which we don't want.
46
+ *
47
+ * This causes downstream plugins using the visitor API to be skipped.
48
+ *
49
+ * This is guarded because the PostCSS API is not public
50
+ * and may change in future versions of PostCSS.
51
+ *
52
+ * See https://github.com/postcss/postcss/issues/1712 for more details
53
+ *
54
+ * @param {import('postcss').Node} node
55
+ */
56
+ function markDirty(node) {
57
+ if (!('markDirty' in node)) {
58
+ return
59
+ }
60
+
61
+ // Traverse the tree down to the leaf nodes
62
+ if (node.nodes) {
63
+ node.nodes.forEach((n) => markDirty(n))
64
+ }
65
+
66
+ // If it's a leaf node mark it as dirty
67
+ // We do this here because marking a node as dirty
68
+ // will walk up the tree and mark all parents as dirty
69
+ // resulting in a lot of unnecessary work if we did this
70
+ // for every single node
71
+ if (!node.nodes) {
72
+ node.markDirty()
73
+ }
74
+ }
75
+
76
+ markDirty(root)
77
+
42
78
  return root
43
79
  }
44
80
  }
@@ -1,9 +1,22 @@
1
- export default function cloneNodes(nodes, source) {
1
+ export default function cloneNodes(nodes, source = undefined, raws = undefined) {
2
2
  return nodes.map((node) => {
3
3
  let cloned = node.clone()
4
4
 
5
5
  if (source !== undefined) {
6
6
  cloned.source = source
7
+
8
+ if ('walk' in cloned) {
9
+ cloned.walk((child) => {
10
+ child.source = source
11
+ })
12
+ }
13
+ }
14
+
15
+ if (raws !== undefined) {
16
+ cloned.raws.tailwind = {
17
+ ...cloned.raws.tailwind,
18
+ ...raws,
19
+ }
7
20
  }
8
21
 
9
22
  return cloned
package/src/util/color.js CHANGED
@@ -2,17 +2,21 @@ import namedColors from 'color-name'
2
2
 
3
3
  let HEX = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i
4
4
  let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i
5
- let VALUE = `(?:\\d+|\\d*\\.\\d+)%?`
6
- let SEP = `(?:\\s*,\\s*|\\s+)`
7
- let ALPHA_SEP = `\\s*[,/]\\s*`
5
+ let VALUE = /(?:\d+|\d*\.\d+)%?/
6
+ let SEP = /(?:\s*,\s*|\s+)/
7
+ let ALPHA_SEP = /\s*[,/]\s*/
8
+ let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/
9
+
8
10
  let RGB = new RegExp(
9
- `^rgba?\\(\\s*(${VALUE})${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\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*\\)$`
10
12
  )
11
13
  let HSL = new RegExp(
12
- `^hsla?\\(\\s*((?:${VALUE})(?:deg|rad|grad|turn)?)${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\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*\\)$`
13
15
  )
14
16
 
15
- 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 } = {}) {
16
20
  if (typeof value !== 'string') {
17
21
  return null
18
22
  }
@@ -40,27 +44,27 @@ export function parseColor(value) {
40
44
  }
41
45
  }
42
46
 
43
- let rgbMatch = value.match(RGB)
47
+ let match = value.match(RGB) ?? value.match(HSL)
44
48
 
45
- if (rgbMatch !== null) {
46
- return {
47
- mode: 'rgb',
48
- color: [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((v) => v.toString()),
49
- alpha: rgbMatch[4]?.toString?.(),
50
- }
49
+ if (match === null) {
50
+ return null
51
51
  }
52
52
 
53
- let hslMatch = value.match(HSL)
53
+ let color = [match[2], match[3], match[4]].filter(Boolean).map((v) => v.toString())
54
54
 
55
- if (hslMatch !== null) {
56
- return {
57
- mode: 'hsl',
58
- color: [hslMatch[1], hslMatch[2], hslMatch[3]].map((v) => v.toString()),
59
- alpha: hslMatch[4]?.toString?.(),
60
- }
55
+ if (!loose && color.length !== 3) {
56
+ return null
57
+ }
58
+
59
+ if (color.length < 3 && !color.some((part) => /^var\(.*?\)$/.test(part))) {
60
+ return null
61
61
  }
62
62
 
63
- return null
63
+ return {
64
+ mode: match[1],
65
+ color,
66
+ alpha: match[5]?.toString?.(),
67
+ }
64
68
  }
65
69
 
66
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) {
@@ -57,7 +63,9 @@ export function number(value) {
57
63
  }
58
64
 
59
65
  export function percentage(value) {
60
- return /%$/g.test(value) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(value))
66
+ return value.split(UNDERSCORE).every((part) => {
67
+ return /%$/g.test(part) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(part))
68
+ })
61
69
  }
62
70
 
63
71
  let lengthUnits = [
@@ -113,7 +121,7 @@ export function color(value) {
113
121
  part = normalize(part)
114
122
 
115
123
  if (part.startsWith('var(')) return true
116
- if (parseColor(part) !== null) return colors++, true
124
+ if (parseColor(part, { loose: true }) !== null) return colors++, true
117
125
 
118
126
  return false
119
127
  })