tailwindcss 0.0.0-insiders.df011dc → 0.0.0-insiders.e2d5f21

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 (120) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/README.md +1 -1
  3. package/colors.js +1 -304
  4. package/defaultConfig.js +1 -4
  5. package/defaultTheme.js +1 -4
  6. package/lib/cli-peer-dependencies.js +4 -9
  7. package/lib/cli.js +646 -705
  8. package/lib/constants.js +15 -15
  9. package/lib/corePluginList.js +174 -4
  10. package/lib/corePlugins.js +3637 -2934
  11. package/lib/css/preflight.css +14 -1
  12. package/lib/featureFlags.js +44 -44
  13. package/lib/index.js +25 -24
  14. package/lib/lib/collapseAdjacentRules.js +33 -35
  15. package/lib/lib/collapseDuplicateDeclarations.js +29 -0
  16. package/lib/lib/detectNesting.js +34 -0
  17. package/lib/lib/evaluateTailwindFunctions.js +134 -155
  18. package/lib/lib/expandApplyAtRules.js +183 -200
  19. package/lib/lib/expandTailwindAtRules.js +219 -232
  20. package/lib/lib/generateRules.js +478 -311
  21. package/lib/lib/getModuleDependencies.js +36 -41
  22. package/lib/lib/normalizeTailwindDirectives.js +66 -58
  23. package/lib/lib/resolveDefaultsAtRules.js +110 -90
  24. package/lib/lib/setupContextUtils.js +740 -580
  25. package/lib/lib/setupTrackingContext.js +142 -162
  26. package/lib/lib/setupWatchingContext.js +235 -278
  27. package/lib/lib/sharedState.js +7 -17
  28. package/lib/lib/substituteScreenAtRules.js +22 -26
  29. package/lib/processTailwindFeatures.js +38 -44
  30. package/lib/public/colors.js +329 -0
  31. package/lib/public/create-plugin.js +13 -0
  32. package/lib/public/default-config.js +14 -0
  33. package/lib/public/default-theme.js +14 -0
  34. package/lib/public/resolve-config.js +19 -0
  35. package/lib/util/bigSign.js +3 -5
  36. package/lib/util/buildMediaQuery.js +15 -31
  37. package/lib/util/cloneDeep.js +14 -13
  38. package/lib/util/cloneNodes.js +9 -13
  39. package/lib/util/color.js +69 -59
  40. package/lib/util/configurePlugins.js +13 -14
  41. package/lib/util/createPlugin.js +21 -24
  42. package/lib/util/createUtilityPlugin.js +47 -45
  43. package/lib/util/dataTypes.js +231 -0
  44. package/lib/util/defaults.js +10 -14
  45. package/lib/util/escapeClassName.js +12 -14
  46. package/lib/util/escapeCommas.js +3 -5
  47. package/lib/util/flattenColorPalette.js +14 -10
  48. package/lib/util/formatVariantSelector.js +186 -0
  49. package/lib/util/getAllConfigs.js +23 -16
  50. package/lib/util/hashConfig.js +10 -10
  51. package/lib/util/isKeyframeRule.js +3 -5
  52. package/lib/util/isPlainObject.js +7 -10
  53. package/lib/util/isValidArbitraryValue.js +64 -0
  54. package/lib/util/log.js +39 -30
  55. package/lib/util/nameClass.js +23 -23
  56. package/lib/util/negateValue.js +14 -15
  57. package/lib/util/normalizeConfig.js +208 -0
  58. package/lib/util/normalizeScreens.js +58 -0
  59. package/lib/util/parseAnimationValue.js +82 -53
  60. package/lib/util/parseBoxShadowValue.js +77 -0
  61. package/lib/util/parseDependency.js +49 -60
  62. package/lib/util/parseObjectStyles.js +23 -20
  63. package/lib/util/pluginUtils.js +153 -285
  64. package/lib/util/prefixSelector.js +14 -16
  65. package/lib/util/resolveConfig.js +194 -249
  66. package/lib/util/resolveConfigPath.js +38 -45
  67. package/lib/util/responsive.js +12 -11
  68. package/lib/util/tap.js +4 -6
  69. package/lib/util/toColorValue.js +4 -5
  70. package/lib/util/toPath.js +4 -6
  71. package/lib/util/transformThemeValue.js +57 -26
  72. package/lib/util/withAlphaVariable.js +56 -54
  73. package/package.json +27 -37
  74. package/peers/index.js +73099 -84294
  75. package/plugin.js +1 -3
  76. package/resolveConfig.js +1 -7
  77. package/scripts/create-plugin-list.js +2 -2
  78. package/src/cli.js +27 -18
  79. package/src/corePluginList.js +1 -1
  80. package/src/corePlugins.js +2124 -1968
  81. package/src/css/preflight.css +14 -1
  82. package/src/featureFlags.js +12 -6
  83. package/src/lib/collapseDuplicateDeclarations.js +28 -0
  84. package/src/lib/detectNesting.js +39 -0
  85. package/src/lib/evaluateTailwindFunctions.js +5 -2
  86. package/src/lib/expandTailwindAtRules.js +24 -32
  87. package/src/lib/generateRules.js +230 -20
  88. package/src/lib/normalizeTailwindDirectives.js +3 -2
  89. package/src/lib/resolveDefaultsAtRules.js +46 -30
  90. package/src/lib/setupContextUtils.js +275 -113
  91. package/src/lib/setupTrackingContext.js +9 -8
  92. package/src/lib/setupWatchingContext.js +12 -7
  93. package/src/lib/sharedState.js +1 -4
  94. package/src/lib/substituteScreenAtRules.js +6 -3
  95. package/src/processTailwindFeatures.js +8 -1
  96. package/src/public/colors.js +300 -0
  97. package/src/public/create-plugin.js +2 -0
  98. package/src/public/default-config.js +4 -0
  99. package/src/public/default-theme.js +4 -0
  100. package/src/public/resolve-config.js +7 -0
  101. package/src/util/buildMediaQuery.js +14 -18
  102. package/src/util/createUtilityPlugin.js +2 -11
  103. package/src/util/dataTypes.js +246 -0
  104. package/src/util/formatVariantSelector.js +196 -0
  105. package/src/util/isValidArbitraryValue.js +61 -0
  106. package/src/util/log.js +18 -21
  107. package/src/util/nameClass.js +10 -6
  108. package/src/util/negateValue.js +4 -2
  109. package/src/util/normalizeConfig.js +249 -0
  110. package/src/util/normalizeScreens.js +42 -0
  111. package/src/util/parseAnimationValue.js +7 -1
  112. package/src/util/parseBoxShadowValue.js +71 -0
  113. package/src/util/parseDependency.js +4 -0
  114. package/src/util/pluginUtils.js +101 -204
  115. package/src/util/prefixSelector.js +1 -4
  116. package/src/util/resolveConfig.js +12 -65
  117. package/src/util/transformThemeValue.js +22 -7
  118. package/src/util/withAlphaVariable.js +13 -8
  119. package/stubs/defaultConfig.stub.js +163 -97
  120. package/stubs/simpleConfig.stub.js +0 -1
@@ -12,6 +12,11 @@
12
12
  border-color: currentColor; /* 2 */
13
13
  }
14
14
 
15
+ ::before,
16
+ ::after {
17
+ --tw-content: '';
18
+ }
19
+
15
20
  /*
16
21
  1. Use a consistent sensible line-height in all browsers.
17
22
  2. Prevent adjustments of font size after orientation changes in iOS.
@@ -283,7 +288,8 @@ legend {
283
288
  }
284
289
 
285
290
  ol,
286
- ul {
291
+ ul,
292
+ menu {
287
293
  list-style: none;
288
294
  margin: 0;
289
295
  padding: 0;
@@ -317,6 +323,13 @@ button,
317
323
  cursor: pointer;
318
324
  }
319
325
 
326
+ /*
327
+ Make sure disabled buttons don't get the pointer cursor.
328
+ */
329
+ :disabled {
330
+ cursor: default;
331
+ }
332
+
320
333
  /*
321
334
  1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
322
335
  2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
@@ -1,18 +1,24 @@
1
1
  import chalk from 'chalk'
2
2
  import log from './util/log'
3
3
 
4
- const featureFlags = {
4
+ let defaults = {
5
+ optimizeUniversalDefaults: true,
6
+ }
7
+
8
+ let featureFlags = {
5
9
  future: [],
6
10
  experimental: ['optimizeUniversalDefaults'],
7
11
  }
8
12
 
9
13
  export function flagEnabled(config, flag) {
10
14
  if (featureFlags.future.includes(flag)) {
11
- return config.future === 'all' || (config?.future?.[flag] ?? false)
15
+ return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false)
12
16
  }
13
17
 
14
18
  if (featureFlags.experimental.includes(flag)) {
15
- return config.experimental === 'all' || (config?.experimental?.[flag] ?? false)
19
+ return (
20
+ config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false)
21
+ )
16
22
  }
17
23
 
18
24
  return false
@@ -34,13 +40,13 @@ export function issueFlagNotices(config) {
34
40
  }
35
41
 
36
42
  if (experimentalFlagsEnabled(config).length > 0) {
37
- const changes = experimentalFlagsEnabled(config)
43
+ let changes = experimentalFlagsEnabled(config)
38
44
  .map((s) => chalk.yellow(s))
39
45
  .join(', ')
40
46
 
41
- log.warn([
47
+ log.warn('experimental-flags-enabled', [
42
48
  `You have enabled experimental features: ${changes}`,
43
- 'Experimental features are not covered by semver, may introduce breaking changes, and can change at any time.',
49
+ 'Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time.',
44
50
  ])
45
51
  }
46
52
  }
@@ -0,0 +1,28 @@
1
+ export default function collapseDuplicateDeclarations() {
2
+ return (root) => {
3
+ root.walkRules((node) => {
4
+ let seen = new Map()
5
+ let droppable = new Set([])
6
+
7
+ node.walkDecls((decl) => {
8
+ // This could happen if we have nested selectors. In that case the
9
+ // parent will loop over all its declarations but also the declarations
10
+ // of nested rules. With this we ensure that we are shallowly checking
11
+ // declarations.
12
+ if (decl.parent !== node) {
13
+ return
14
+ }
15
+
16
+ if (seen.has(decl.prop)) {
17
+ droppable.add(seen.get(decl.prop))
18
+ }
19
+
20
+ seen.set(decl.prop, decl)
21
+ })
22
+
23
+ for (let decl of droppable) {
24
+ decl.remove()
25
+ }
26
+ })
27
+ }
28
+ }
@@ -0,0 +1,39 @@
1
+ export default function (_context) {
2
+ return (root, result) => {
3
+ let found = false
4
+
5
+ root.walkAtRules('tailwind', (node) => {
6
+ if (found) return false
7
+
8
+ if (node.parent && node.parent.type !== 'root') {
9
+ found = true
10
+ node.warn(
11
+ result,
12
+ [
13
+ 'Nested @tailwind rules were detected, but are not supported.',
14
+ "Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
15
+ 'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
16
+ ].join('\n')
17
+ )
18
+ return false
19
+ }
20
+ })
21
+
22
+ root.walkRules((rule) => {
23
+ if (found) return false
24
+
25
+ rule.walkRules((nestedRule) => {
26
+ found = true
27
+ nestedRule.warn(
28
+ result,
29
+ [
30
+ 'Nested CSS was detected, but CSS nesting has not been configured correctly.',
31
+ 'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
32
+ 'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
33
+ ].join('\n')
34
+ )
35
+ return false
36
+ })
37
+ })
38
+ }
39
+ }
@@ -2,6 +2,7 @@ import dlv from 'dlv'
2
2
  import didYouMean from 'didyoumean'
3
3
  import transformThemeValue from '../util/transformThemeValue'
4
4
  import parseValue from 'postcss-value-parser'
5
+ import { normalizeScreens } from '../util/normalizeScreens'
5
6
  import buildMediaQuery from '../util/buildMediaQuery'
6
7
  import { toPath } from '../util/toPath'
7
8
 
@@ -173,12 +174,14 @@ export default function ({ tailwindConfig: config }) {
173
174
  },
174
175
  screen: (node, screen) => {
175
176
  screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
177
+ let screens = normalizeScreens(config.theme.screens)
178
+ let screenDefinition = screens.find(({ name }) => name === screen)
176
179
 
177
- if (config.theme.screens[screen] === undefined) {
180
+ if (!screenDefinition) {
178
181
  throw node.error(`The '${screen}' screen does not exist in your theme.`)
179
182
  }
180
183
 
181
- return buildMediaQuery(config.theme.screens[screen])
184
+ return buildMediaQuery(screenDefinition)
182
185
  },
183
186
  }
184
187
  return (root) => {
@@ -1,16 +1,24 @@
1
+ import LRU from 'quick-lru'
1
2
  import * as sharedState from './sharedState'
2
3
  import { generateRules } from './generateRules'
3
4
  import bigSign from '../util/bigSign'
4
5
  import cloneNodes from '../util/cloneNodes'
5
6
 
6
7
  let env = sharedState.env
7
- let contentMatchCache = sharedState.contentMatchCache
8
8
 
9
9
  const PATTERNS = [
10
- "([^<>\"'`\\s]*\\['[^<>\"'`\\s]*'\\])", // `content-['hello']` but not `content-['hello']']`
11
- '([^<>"\'`\\s]*\\["[^<>"\'`\\s]*"\\])', // `content-["hello"]` but not `content-["hello"]"]`
12
- '([^<>"\'`\\s]*\\[[^<>"\'`\\s]+\\])', // `fill-[#bada55]`
13
- '([^<>"\'`\\s]*[^<>"\'`\\s:])', // `px-1.5`, `uppercase` but not `uppercase:`
10
+ /([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
11
+ /([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
12
+ /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
13
+ /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
14
+ /([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
15
+ /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
16
+ /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
17
+ /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
18
+ /([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
19
+ /([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
20
+ /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
21
+ /([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
14
22
  ].join('|')
15
23
  const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
16
24
  const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
@@ -31,21 +39,6 @@ const builtInTransformers = {
31
39
 
32
40
  function getExtractor(tailwindConfig, fileExtension) {
33
41
  let extractors = tailwindConfig.content.extract
34
- let contentOptions = tailwindConfig.content.options
35
-
36
- if (typeof extractors === 'function') {
37
- extractors = {
38
- DEFAULT: extractors,
39
- }
40
- }
41
- if (contentOptions.defaultExtractor) {
42
- extractors.DEFAULT = contentOptions.defaultExtractor
43
- }
44
- for (let { extensions, extractor } of contentOptions.extractors || []) {
45
- for (let extension of extensions) {
46
- extractors[extension] = extractor
47
- }
48
- }
49
42
 
50
43
  return (
51
44
  extractors[fileExtension] ||
@@ -58,12 +51,6 @@ function getExtractor(tailwindConfig, fileExtension) {
58
51
  function getTransformer(tailwindConfig, fileExtension) {
59
52
  let transformers = tailwindConfig.content.transform
60
53
 
61
- if (typeof transformers === 'function') {
62
- transformers = {
63
- DEFAULT: transformers,
64
- }
65
- }
66
-
67
54
  return (
68
55
  transformers[fileExtension] ||
69
56
  transformers.DEFAULT ||
@@ -72,10 +59,16 @@ function getTransformer(tailwindConfig, fileExtension) {
72
59
  )
73
60
  }
74
61
 
62
+ let extractorCache = new WeakMap()
63
+
75
64
  // Scans template contents for possible classes. This is a hot path on initial build but
76
65
  // not too important for subsequent builds. The faster the better though — if we can speed
77
66
  // up these regexes by 50% that could cut initial build time by like 20%.
78
- function getClassCandidates(content, extractor, contentMatchCache, candidates, seen) {
67
+ function getClassCandidates(content, extractor, candidates, seen) {
68
+ if (!extractorCache.has(extractor)) {
69
+ extractorCache.set(extractor, new LRU({ maxSize: 25000 }))
70
+ }
71
+
79
72
  for (let line of content.split('\n')) {
80
73
  line = line.trim()
81
74
 
@@ -84,8 +77,8 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
84
77
  }
85
78
  seen.add(line)
86
79
 
87
- if (contentMatchCache.has(line)) {
88
- for (let match of contentMatchCache.get(line)) {
80
+ if (extractorCache.get(extractor).has(line)) {
81
+ for (let match of extractorCache.get(extractor).get(line)) {
89
82
  candidates.add(match)
90
83
  }
91
84
  } else {
@@ -96,7 +89,7 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
96
89
  candidates.add(match)
97
90
  }
98
91
 
99
- contentMatchCache.set(line, lineMatchesSet)
92
+ extractorCache.get(extractor).set(line, lineMatchesSet)
100
93
  }
101
94
  }
102
95
  }
@@ -185,7 +178,7 @@ export default function expandTailwindAtRules(context) {
185
178
  for (let { content, extension } of context.changedContent) {
186
179
  let transformer = getTransformer(context.tailwindConfig, extension)
187
180
  let extractor = getExtractor(context.tailwindConfig, extension)
188
- getClassCandidates(transformer(content), extractor, contentMatchCache, candidates, seen)
181
+ getClassCandidates(transformer(content), extractor, candidates, seen)
189
182
  }
190
183
 
191
184
  // ---
@@ -246,7 +239,6 @@ export default function expandTailwindAtRules(context) {
246
239
  if (env.DEBUG) {
247
240
  console.log('Potential classes: ', candidates.size)
248
241
  console.log('Active contexts: ', sharedState.contextSourcesMap.size)
249
- console.log('Content match entries', contentMatchCache.size)
250
242
  }
251
243
 
252
244
  // Clear the cache for the changed files
@@ -4,6 +4,11 @@ import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
6
  import { updateAllClasses } from '../util/pluginUtils'
7
+ import log from '../util/log'
8
+ import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
9
+ import { asClass } from '../util/nameClass'
10
+ import { normalize } from '../util/dataTypes'
11
+ import isValidArbitraryValue from '../util/isValidArbitraryValue'
7
12
 
8
13
  let classNameParser = selectorParser((selectors) => {
9
14
  return selectors.first.filter(({ type }) => type === 'class').pop().value
@@ -107,16 +112,21 @@ function applyVariant(variant, matches, context) {
107
112
  let result = []
108
113
 
109
114
  for (let [meta, rule] of matches) {
110
- if (meta.options.respectVariants === false) {
111
- result.push([meta, rule])
112
- continue
113
- }
114
-
115
115
  let container = postcss.root({ nodes: [rule.clone()] })
116
116
 
117
117
  for (let [variantSort, variantFunction] of variantFunctionTuples) {
118
118
  let clone = container.clone()
119
+ let collectedFormats = []
120
+
121
+ let originals = new Map()
122
+
123
+ function prepareBackup() {
124
+ if (originals.size > 0) return // Already prepared, chicken out
125
+ clone.walkRules((rule) => originals.set(rule, rule.selector))
126
+ }
127
+
119
128
  function modifySelectors(modifierFunction) {
129
+ prepareBackup()
120
130
  clone.each((rule) => {
121
131
  if (rule.type !== 'rule') {
122
132
  return
@@ -131,20 +141,84 @@ function applyVariant(variant, matches, context) {
131
141
  })
132
142
  })
133
143
  })
144
+
134
145
  return clone
135
146
  }
136
147
 
137
148
  let ruleWithVariant = variantFunction({
138
- container: clone,
149
+ // Public API
150
+ get container() {
151
+ prepareBackup()
152
+ return clone
153
+ },
139
154
  separator: context.tailwindConfig.separator,
140
155
  modifySelectors,
156
+
157
+ // Private API for now
158
+ wrap(wrapper) {
159
+ let nodes = clone.nodes
160
+ clone.removeAll()
161
+ wrapper.append(nodes)
162
+ clone.append(wrapper)
163
+ },
164
+ format(selectorFormat) {
165
+ collectedFormats.push(selectorFormat)
166
+ },
141
167
  })
142
168
 
169
+ if (typeof ruleWithVariant === 'string') {
170
+ collectedFormats.push(ruleWithVariant)
171
+ }
172
+
143
173
  if (ruleWithVariant === null) {
144
174
  continue
145
175
  }
146
176
 
147
- let withOffset = [{ ...meta, sort: variantSort | meta.sort }, clone.nodes[0]]
177
+ // We filled the `originals`, therefore we assume that somebody touched
178
+ // `container` or `modifySelectors`. Let's see if they did, so that we
179
+ // can restore the selectors, and collect the format strings.
180
+ if (originals.size > 0) {
181
+ clone.walkRules((rule) => {
182
+ if (!originals.has(rule)) return
183
+ let before = originals.get(rule)
184
+ if (before === rule.selector) return // No mutation happened
185
+
186
+ let modified = rule.selector
187
+
188
+ // Rebuild the base selector, this is what plugin authors would do
189
+ // as well. E.g.: `${variant}${separator}${className}`.
190
+ // However, plugin authors probably also prepend or append certain
191
+ // classes, pseudos, ids, ...
192
+ let rebuiltBase = selectorParser((selectors) => {
193
+ selectors.walkClasses((classNode) => {
194
+ classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
195
+ })
196
+ }).processSync(before)
197
+
198
+ // Now that we know the original selector, the new selector, and
199
+ // the rebuild part in between, we can replace the part that plugin
200
+ // authors need to rebuild with `&`, and eventually store it in the
201
+ // collectedFormats. Similar to what `format('...')` would do.
202
+ //
203
+ // E.g.:
204
+ // variant: foo
205
+ // selector: .markdown > p
206
+ // modified (by plugin): .foo .foo\\:markdown > p
207
+ // rebuiltBase (internal): .foo\\:markdown > p
208
+ // format: .foo &
209
+ collectedFormats.push(modified.replace(rebuiltBase, '&'))
210
+ rule.selector = before
211
+ })
212
+ }
213
+
214
+ let withOffset = [
215
+ {
216
+ ...meta,
217
+ sort: variantSort | meta.sort,
218
+ collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
219
+ },
220
+ clone.nodes[0],
221
+ ]
148
222
  result.push(withOffset)
149
223
  }
150
224
  }
@@ -174,21 +248,57 @@ function parseRules(rule, cache, options = {}) {
174
248
  return [cache.get(rule), options]
175
249
  }
176
250
 
251
+ function extractArbitraryProperty(classCandidate, context) {
252
+ let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
253
+
254
+ if (value === undefined) {
255
+ return null
256
+ }
257
+
258
+ let normalized = normalize(value)
259
+
260
+ if (!isValidArbitraryValue(normalized)) {
261
+ return null
262
+ }
263
+
264
+ return [
265
+ [
266
+ { sort: context.arbitraryPropertiesSort, layer: 'utilities' },
267
+ () => ({
268
+ [asClass(classCandidate)]: {
269
+ [property]: normalized,
270
+ },
271
+ }),
272
+ ],
273
+ ]
274
+ }
275
+
177
276
  function* resolveMatchedPlugins(classCandidate, context) {
178
277
  if (context.candidateRuleMap.has(classCandidate)) {
179
278
  yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
180
279
  }
181
280
 
281
+ yield* (function* (arbitraryPropertyRule) {
282
+ if (arbitraryPropertyRule !== null) {
283
+ yield [arbitraryPropertyRule, 'DEFAULT']
284
+ }
285
+ })(extractArbitraryProperty(classCandidate, context))
286
+
182
287
  let candidatePrefix = classCandidate
183
288
  let negative = false
184
289
 
185
- const twConfigPrefix = context.tailwindConfig.prefix || ''
290
+ const twConfigPrefix = context.tailwindConfig.prefix
291
+
186
292
  const twConfigPrefixLen = twConfigPrefix.length
187
293
  if (candidatePrefix[twConfigPrefixLen] === '-') {
188
294
  negative = true
189
295
  candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
190
296
  }
191
297
 
298
+ if (negative && context.candidateRuleMap.has(candidatePrefix)) {
299
+ yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
300
+ }
301
+
192
302
  for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
193
303
  if (context.candidateRuleMap.has(prefix)) {
194
304
  yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
@@ -225,28 +335,94 @@ function* resolveMatches(candidate, context) {
225
335
 
226
336
  for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
227
337
  let matches = []
338
+ let typesByMatches = new Map()
339
+
228
340
  let [plugins, modifier] = matchedPlugins
341
+ let isOnlyPlugin = plugins.length === 1
229
342
 
230
343
  for (let [sort, plugin] of plugins) {
344
+ let matchesPerPlugin = []
345
+
231
346
  if (typeof plugin === 'function') {
232
- for (let ruleSet of [].concat(plugin(modifier))) {
347
+ for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
233
348
  let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
234
349
  for (let rule of rules) {
235
- matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
350
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
236
351
  }
237
352
  }
238
353
  }
239
354
  // Only process static plugins on exact matches
240
- else if (modifier === 'DEFAULT') {
355
+ else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
241
356
  let ruleSet = plugin
242
357
  let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
243
358
  for (let rule of rules) {
244
- matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
359
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
245
360
  }
246
361
  }
362
+
363
+ if (matchesPerPlugin.length > 0) {
364
+ typesByMatches.set(matchesPerPlugin, sort.options?.type)
365
+ matches.push(matchesPerPlugin)
366
+ }
247
367
  }
248
368
 
249
- matches = applyPrefix(matches, context)
369
+ // Only keep the result of the very first plugin if we are dealing with
370
+ // arbitrary values, to protect against ambiguity.
371
+ if (isArbitraryValue(modifier) && matches.length > 1) {
372
+ let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
373
+
374
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
375
+ for (let pluginTypes of typesPerPlugin) {
376
+ for (let type of pluginTypes) {
377
+ let removeFromOwnGroup = false
378
+
379
+ for (let otherGroup of typesPerPlugin) {
380
+ if (pluginTypes === otherGroup) continue
381
+
382
+ if (otherGroup.has(type)) {
383
+ otherGroup.delete(type)
384
+ removeFromOwnGroup = true
385
+ }
386
+ }
387
+
388
+ if (removeFromOwnGroup) pluginTypes.delete(type)
389
+ }
390
+ }
391
+
392
+ let messages = []
393
+
394
+ for (let [idx, group] of typesPerPlugin.entries()) {
395
+ for (let type of group) {
396
+ let rules = matches[idx]
397
+ .map(([, rule]) => rule)
398
+ .flat()
399
+ .map((rule) =>
400
+ rule
401
+ .toString()
402
+ .split('\n')
403
+ .slice(1, -1) // Remove selector and closing '}'
404
+ .map((line) => line.trim())
405
+ .map((x) => ` ${x}`) // Re-indent
406
+ .join('\n')
407
+ )
408
+ .join('\n\n')
409
+
410
+ messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
411
+ break
412
+ }
413
+ }
414
+
415
+ log.warn([
416
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
417
+ ...messages,
418
+ `If this is content and not a class, replace it with \`${candidate
419
+ .replace('[', '&lsqb;')
420
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
421
+ ])
422
+ continue
423
+ }
424
+
425
+ matches = applyPrefix(matches.flat(), context)
250
426
 
251
427
  if (important) {
252
428
  matches = applyImportant(matches, context)
@@ -257,6 +433,22 @@ function* resolveMatches(candidate, context) {
257
433
  }
258
434
 
259
435
  for (let match of matches) {
436
+ // Apply final format selector
437
+ if (match[0].collectedFormats) {
438
+ let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
439
+ let container = postcss.root({ nodes: [match[1].clone()] })
440
+ container.walkRules((rule) => {
441
+ if (inKeyframes(rule)) return
442
+
443
+ rule.selector = finalizeSelector(finalFormat, {
444
+ selector: rule.selector,
445
+ candidate,
446
+ context,
447
+ })
448
+ })
449
+ match[1] = container.nodes[0]
450
+ }
451
+
260
452
  yield match
261
453
  }
262
454
  }
@@ -290,30 +482,48 @@ function generateRules(candidates, context) {
290
482
  allRules.push(matches)
291
483
  }
292
484
 
293
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
294
- if (options.respectImportant) {
295
- if (context.tailwindConfig.important === true) {
485
+ // Strategy based on `tailwindConfig.important`
486
+ let strategy = ((important) => {
487
+ if (important === true) {
488
+ return (rule) => {
296
489
  rule.walkDecls((d) => {
297
490
  if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
298
491
  d.important = true
299
492
  }
300
493
  })
301
- } else if (typeof context.tailwindConfig.important === 'string') {
494
+ }
495
+ }
496
+
497
+ if (typeof important === 'string') {
498
+ return (rule) => {
499
+ rule.selectors = rule.selectors.map((selector) => {
500
+ return `${important} ${selector}`
501
+ })
502
+ }
503
+ }
504
+ })(context.tailwindConfig.important)
505
+
506
+ return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
507
+ if (options.respectImportant) {
508
+ if (strategy) {
302
509
  let container = postcss.root({ nodes: [rule.clone()] })
303
510
  container.walkRules((r) => {
304
511
  if (inKeyframes(r)) {
305
512
  return
306
513
  }
307
514
 
308
- r.selectors = r.selectors.map((selector) => {
309
- return `${context.tailwindConfig.important} ${selector}`
310
- })
515
+ strategy(r)
311
516
  })
312
517
  rule = container.nodes[0]
313
518
  }
314
519
  }
520
+
315
521
  return [sort | context.layerOrder[layer], rule]
316
522
  })
317
523
  }
318
524
 
525
+ function isArbitraryValue(input) {
526
+ return input.startsWith('[') && input.endsWith(']')
527
+ }
528
+
319
529
  export { resolveMatches, generateRules }
@@ -41,8 +41,9 @@ export default function normalizeTailwindDirectives(root) {
41
41
 
42
42
  if (['layer', 'responsive', 'variants'].includes(atRule.name)) {
43
43
  if (['responsive', 'variants'].includes(atRule.name)) {
44
- log.warn([
45
- `'@${atRule.name}' is deprecated, use '@layer utilities' or '@layer components' instead.`,
44
+ log.warn(`${atRule.name}-at-rule-deprecated`, [
45
+ `The \`@${atRule.name}\` directive has been deprecated in Tailwind CSS v3.0.`,
46
+ `Use \`@layer utilities\` or \`@layer components\` instead.`,
46
47
  ])
47
48
  }
48
49
  layerDirectives.add(atRule)